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/08/17 04:40:49 UTC

zeppelin git commit: [ZEPPELIN-3702]. Followup of ZEPPELIN-3681: Use NotebookService in more apis

Repository: zeppelin
Updated Branches:
  refs/heads/master 48109b256 -> 09d44d504


[ZEPPELIN-3702]. Followup of ZEPPELIN-3681: Use NotebookService in more apis

### What is this PR for?
Followup of ZEPPELIN-3681, use NotebookService in more apis. and also add unit test for NotebookService.

### What type of PR is it?
[Refactoring]

### Todos
* [ ] - Task

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

### How should this be tested?
* CI pass

### Screenshots (if appropriate)

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

Author: Jeff Zhang <zj...@apache.org>

Closes #3133 from zjffdu/ZEPPELIN-3702 and squashes the following commits:

c64f04ff0 [Jeff Zhang] [ZEPPELIN-3702]. Followup of ZEPPELIN-3681: Use NotebookService in more apis


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

Branch: refs/heads/master
Commit: 09d44d504aed8386df633356f82cb3e54f6278fd
Parents: 48109b2
Author: Jeff Zhang <zj...@apache.org>
Authored: Fri Aug 10 15:00:12 2018 +0800
Committer: Jeff Zhang <zj...@apache.org>
Committed: Fri Aug 17 12:40:26 2018 +0800

----------------------------------------------------------------------
 .../notebook/repo/NotebookRepoSyncTest.java     |  10 +-
 .../apache/zeppelin/realm/ZeppelinHubRealm.java |   8 +-
 .../zeppelin/rest/NotebookRepoRestApi.java      |  24 ++-
 .../apache/zeppelin/rest/NotebookRestApi.java   | 100 ++++------
 .../zeppelin/service/NotebookService.java       |  48 +++--
 .../apache/zeppelin/socket/NotebookServer.java  | 132 +++++--------
 .../zeppelin/rest/AbstractTestRestApi.java      |   6 +
 .../zeppelin/rest/ZeppelinRestApiTest.java      |   7 +-
 .../zeppelin/service/NotebookServiceTest.java   | 183 +++++++++++++++++++
 .../helium/HeliumApplicationFactory.java        |  21 ---
 .../java/org/apache/zeppelin/notebook/Note.java |   7 +-
 .../org/apache/zeppelin/notebook/Notebook.java  |  16 +-
 .../notebook/NotebookEventListener.java         |   2 -
 .../interpreter/AbstractInterpreterTest.java    |   2 +-
 .../apache/zeppelin/notebook/FolderTest.java    |   8 +-
 .../zeppelin/notebook/FolderViewTest.java       |   2 +-
 .../org/apache/zeppelin/notebook/NoteTest.java  |  31 ++--
 .../apache/zeppelin/notebook/NotebookTest.java  |   6 -
 .../zeppelin/search/LuceneSearchTest.java       |   1 +
 19 files changed, 377 insertions(+), 237 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-plugins/notebookrepo/git/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-plugins/notebookrepo/git/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java b/zeppelin-plugins/notebookrepo/git/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
index 54ce606..5cc7289 100644
--- a/zeppelin-plugins/notebookrepo/git/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
+++ b/zeppelin-plugins/notebookrepo/git/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
@@ -139,7 +139,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
     assertEquals(0, notebookRepoSync.list(1, anonymous).size());
 
     /* create note */
-    Note note = notebookSync.createNote("test", anonymous);
+    Note note = notebookSync.createNote("test", "", anonymous);
 
     // check that automatically saved on both storages
     assertEquals(1, notebookRepoSync.list(0, anonymous).size());
@@ -156,7 +156,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
     assertEquals(0, notebookRepoSync.list(0, anonymous).size());
     assertEquals(0, notebookRepoSync.list(1, anonymous).size());
 
-    Note note = notebookSync.createNote("test", anonymous);
+    Note note = notebookSync.createNote("test", "", anonymous);
 
     /* check that created in both storage systems */
     assertEquals(1, notebookRepoSync.list(0, anonymous).size());
@@ -176,7 +176,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
   public void testSyncUpdateMain() throws IOException {
 
     /* create note */
-    Note note = notebookSync.createNote("test", anonymous);
+    Note note = notebookSync.createNote("test", "", anonymous);
     Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     Map config = p1.getConfig();
     config.put("enabled", true);
@@ -305,7 +305,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
     // no notes
     assertThat(vRepoSync.list(anonymous).size()).isEqualTo(0);
     // create note
-    Note note = vNotebookSync.createNote("test", anonymous);
+    Note note = vNotebookSync.createNote("test", "", anonymous);
     assertThat(vRepoSync.list(anonymous).size()).isEqualTo(1);
 
     String noteId = vRepoSync.list(anonymous).get(0).getId();
@@ -331,7 +331,7 @@ public class NotebookRepoSyncTest implements JobListenerFactory {
   public void testSyncWithAcl() throws IOException {
     /* scenario 1 - note exists with acl on main storage */
     AuthenticationInfo user1 = new AuthenticationInfo("user1");
-    Note note = notebookSync.createNote("test", user1);
+    Note note = notebookSync.createNote("test", "", user1);
     assertEquals(0, note.getParagraphs().size());
 
     // saved on both storages

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
index 265ecbd..2a4dcda 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java
@@ -34,6 +34,7 @@ import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.zeppelin.service.ServiceContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -224,8 +225,13 @@ public class ZeppelinHubRealm extends AuthorizingRealm {
     /* TODO(xxx): add proper roles */
     HashSet<String> userAndRoles = new HashSet<>();
     userAndRoles.add(username);
-    ZeppelinServer.notebookWsServer.broadcastReloadedNoteList(
+    ServiceContext context = new ServiceContext(
         new org.apache.zeppelin.user.AuthenticationInfo(username), userAndRoles);
+    try {
+      ZeppelinServer.notebookWsServer.broadcastReloadedNoteList(null, context);
+    } catch (IOException e) {
+      LOG.error("Fail to broadcastReloadedNoteList", e);
+    }
 
     ZeppelinhubUtils.userLoginRoutine(username);
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
index 9127ac0..d9ac664 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
@@ -17,14 +17,18 @@
 package org.apache.zeppelin.rest;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
 import com.google.gson.JsonSyntaxException;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.service.ServiceContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.PUT;
@@ -82,10 +86,22 @@ public class NotebookRepoRestApi {
   public Response refreshRepo(){
     AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
     LOG.info("Reloading notebook repository for user {}", subject.getUser());
-    notebookWsServer.broadcastReloadedNoteList(subject, null);
+    try {
+      notebookWsServer.broadcastReloadedNoteList(null, getServiceContext());
+    } catch (IOException e) {
+      LOG.error("Fail to refresh repo", e);
+    }
     return new JsonResponse<>(Status.OK, "", null).build();
   }
 
+  private ServiceContext getServiceContext() {
+    AuthenticationInfo authInfo = new AuthenticationInfo(SecurityUtils.getPrincipal());
+    Set<String> userAndRoles = Sets.newHashSet();
+    userAndRoles.add(SecurityUtils.getPrincipal());
+    userAndRoles.addAll(SecurityUtils.getAssociatedRoles());
+    return new ServiceContext(authInfo, userAndRoles);
+  }
+
   /**
    * Update a specific note repo.
    *
@@ -118,7 +134,11 @@ public class NotebookRepoRestApi {
         noteRepos.updateNotebookRepo(newSettings.name, newSettings.settings, subject);
     if (!updatedSettings.isEmpty()) {
       LOG.info("Broadcasting note list to user {}", subject.getUser());
-      notebookWsServer.broadcastReloadedNoteList(subject, null);
+      try {
+        notebookWsServer.broadcastReloadedNoteList(null, getServiceContext());
+      } catch (IOException e) {
+        LOG.error("Fail to refresh repo.", e);
+      }
     }
     return new JsonResponse<>(Status.OK, "", updatedSettings).build();
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index 90647f4..c9e0345 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -22,6 +22,7 @@ import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.zeppelin.annotation.ZeppelinApi;
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.Notebook;
@@ -75,7 +76,9 @@ import java.util.Set;
 @Produces("application/json")
 public class NotebookRestApi {
   private static final Logger LOG = LoggerFactory.getLogger(NotebookRestApi.class);
-  Gson gson = new Gson();
+  private static Gson gson = new Gson();
+
+  private ZeppelinConfiguration zConf;
   private Notebook notebook;
   private NotebookServer notebookServer;
   private SearchService noteSearchService;
@@ -91,6 +94,7 @@ public class NotebookRestApi {
     this.notebookService = new NotebookService(notebook);
     this.noteSearchService = search;
     this.notebookAuthorization = notebook.getNotebookAuthorization();
+    this.zConf = notebook.getConf();
   }
 
   /**
@@ -284,7 +288,7 @@ public class NotebookRestApi {
   @ZeppelinApi
   public Response getNoteList() throws IOException {
     List<Map<String, String>> notesInfo = notebookService.listNotes(false, getServiceContext(),
-        new RestServiceCallback<List<Map<String, String>>>());
+        new RestServiceCallback());
     return new JsonResponse<>(Status.OK, "", notesInfo).build();
   }
 
@@ -293,7 +297,7 @@ public class NotebookRestApi {
   @ZeppelinApi
   public Response getNote(@PathParam("noteId") String noteId) throws IOException {
     Note note =
-        notebookService.getNote(noteId, getServiceContext(), new RestServiceCallback<Note>());
+        notebookService.getNote(noteId, getServiceContext(), new RestServiceCallback());
     return new JsonResponse<>(Status.OK, "", note).build();
   }
 
@@ -325,7 +329,7 @@ public class NotebookRestApi {
   @ZeppelinApi
   public Response importNote(String noteJson) throws IOException {
     Note note = notebookService.importNote(null, noteJson, getServiceContext(),
-        new RestServiceCallback<Note>());
+        new RestServiceCallback());
     return new JsonResponse<>(Status.OK, "", note.getId()).build();
   }
 
@@ -343,28 +347,18 @@ public class NotebookRestApi {
     String user = SecurityUtils.getPrincipal();
     LOG.info("Create new note by JSON {}", message);
     NewNoteRequest request = NewNoteRequest.fromJson(message);
-    AuthenticationInfo subject = new AuthenticationInfo(user);
-    Note note = notebook.createNote(subject);
-    if (request != null) {
-      List<NewParagraphRequest> initialParagraphs = request.getParagraphs();
-      if (initialParagraphs != null) {
-        for (NewParagraphRequest paragraphRequest : initialParagraphs) {
-          Paragraph p = note.addNewParagraph(subject);
-          initParagraph(p, paragraphRequest, user);
-        }
+    Note note = notebookService.createNote(
+        request.getName(),
+        zConf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT),
+        getServiceContext(),
+        new RestServiceCallback<>());
+    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
+    if (request.getParagraphs() != null) {
+      for (NewParagraphRequest paragraphRequest : request.getParagraphs()) {
+        Paragraph p = note.addNewParagraph(subject);
+        initParagraph(p, paragraphRequest, user);
       }
     }
-    note.addNewParagraph(subject); // add one paragraph to the last
-    String noteName = request.getName();
-    if (noteName.isEmpty()) {
-      noteName = "Note " + note.getId();
-    }
-
-    note.setName(noteName);
-    note.persist(subject);
-    note.setCronSupported(notebook.getConf());
-    notebookServer.broadcastNote(note);
-    notebookServer.broadcastNoteList(subject, SecurityUtils.getAssociatedRoles());
     return new JsonResponse<>(Status.OK, "", note.getId()).build();
   }
 
@@ -415,7 +409,7 @@ public class NotebookRestApi {
     }
     AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
     Note newNote = notebookService.cloneNote(noteId, newNoteName, getServiceContext(),
-        new SimpleServiceCallback<Note>(){
+        new RestServiceCallback<Note>(){
           @Override
           public void onSuccess(Note newNote, ServiceContext context) throws IOException {
             notebookServer.broadcastNote(newNote);
@@ -439,7 +433,6 @@ public class NotebookRestApi {
                              String message) throws IOException {
     LOG.info("rename note by JSON {}", message);
     RenameNoteRequest request = gson.fromJson(message, RenameNoteRequest.class);
-
     String newName = request.getName();
     if (newName.isEmpty()) {
       LOG.warn("Trying to rename notebook {} with empty name parameter", noteId);
@@ -584,11 +577,6 @@ public class NotebookRestApi {
                                 @PathParam("newIndex") String newIndex)
       throws IOException {
     LOG.info("move paragraph {} {} {}", noteId, paragraphId, newIndex);
-
-    Note note = notebook.getNote(noteId);
-    checkIfNoteIsNotNull(note);
-    checkIfUserCanWrite(noteId, "Insufficient privileges you cannot move paragraph");
-
     notebookService.moveParagraph(noteId, paragraphId, Integer.parseInt(newIndex),
         getServiceContext(),
         new RestServiceCallback<Paragraph>() {
@@ -614,7 +602,6 @@ public class NotebookRestApi {
   public Response deleteParagraph(@PathParam("noteId") String noteId,
                                   @PathParam("paragraphId") String paragraphId) throws IOException {
     LOG.info("delete paragraph {} {}", noteId, paragraphId);
-
     notebookService.removeParagraph(noteId, paragraphId, getServiceContext(),
         new RestServiceCallback<Paragraph>() {
           @Override
@@ -638,12 +625,8 @@ public class NotebookRestApi {
   public Response clearAllParagraphOutput(@PathParam("noteId") String noteId)
       throws IOException {
     LOG.info("clear all paragraph output of note {}", noteId);
-    checkIfUserCanWrite(noteId, "Insufficient privileges you cannot clear this note");
-
-    Note note = notebook.getNote(noteId);
-    checkIfNoteIsNotNull(note);
-    note.clearAllParagraphOutput();
-
+    notebookService.clearAllParagraphOutput(noteId, getServiceContext(),
+        new RestServiceCallback<>());
     return new JsonResponse(Status.OK, "").build();
   }
 
@@ -777,7 +760,7 @@ public class NotebookRestApi {
       params = request.getParams();
     }
     notebookService.runParagraph(noteId, paragraphId, "", "", params,
-        new HashMap<String, Object>(), false, getServiceContext(), new RestServiceCallback<>());
+        new HashMap<>(), false, false, getServiceContext(), new RestServiceCallback<>());
     return new JsonResponse<>(Status.OK).build();
   }
 
@@ -801,31 +784,24 @@ public class NotebookRestApi {
       throws IOException, IllegalArgumentException {
     LOG.info("run paragraph synchronously {} {} {}", noteId, paragraphId, message);
 
-    Note note = notebook.getNote(noteId);
-    checkIfNoteIsNotNull(note);
-    checkIfUserCanRun(noteId, "Insufficient privileges you cannot run paragraph");
-    Paragraph paragraph = note.getParagraph(paragraphId);
-    checkIfParagraphIsNotNull(paragraph);
-
-    // handle params if presented
-    handleParagraphParams(message, note, paragraph);
-
-    if (paragraph.getListener() == null) {
-      note.initializeJobListenerForParagraph(paragraph);
+    Map<String, Object> params = new HashMap<>();
+    if (!StringUtils.isEmpty(message)) {
+      RunParagraphWithParametersRequest request =
+          RunParagraphWithParametersRequest.fromJson(message);
+      params = request.getParams();
     }
-
-    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
-    subject.setRoles(new LinkedList<>(SecurityUtils.getAssociatedRoles()));
-    paragraph.setAuthenticationInfo(subject);
-
-    paragraph.run();
-
-    final InterpreterResult result = paragraph.getResult();
-
-    if (result.code() == InterpreterResult.Code.SUCCESS) {
-      return new JsonResponse<>(Status.OK, result).build();
+    if (notebookService.runParagraph(noteId, paragraphId, "", "", params,
+        new HashMap<>(), false, true, getServiceContext(), new RestServiceCallback<>())) {
+      Note note = notebookService.getNote(noteId, getServiceContext(), new RestServiceCallback<>());
+      Paragraph p = note.getParagraph(paragraphId);
+      InterpreterResult result = p.getResult();
+      if (result.code() == InterpreterResult.Code.SUCCESS) {
+        return new JsonResponse<>(Status.OK, result).build();
+      } else {
+        return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, result).build();
+      }
     } else {
-      return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, result).build();
+      return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, "Fail to run paragraph").build();
     }
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 401420f..e7a5f03 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
@@ -17,7 +17,10 @@
 
 package org.apache.zeppelin.service;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterNotFoundException;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
 import org.apache.zeppelin.notebook.Folder;
@@ -72,8 +75,6 @@ public class NotebookService {
             callback)) {
           return null;
         }
-      } else {
-        callback.onFailure(new Exception("configured HomePage is not existed"), context);
       }
     }
     callback.onSuccess(note, context);
@@ -101,19 +102,19 @@ public class NotebookService {
   }
 
 
-  public Note createNote(String defaultInterpreterGroup,
-                         String noteName,
+  public Note createNote(String noteName,
+                         String defaultInterpreterGroup,
                          ServiceContext context,
                          ServiceCallback<Note> callback) throws IOException {
     if (defaultInterpreterGroup == null) {
       defaultInterpreterGroup = zConf.getString(
           ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT);
     }
-    if (noteName == null) {
+    if (StringUtils.isBlank(noteName)) {
       noteName = "Untitled Note";
     }
     try {
-      Note note = notebook.createNote(defaultInterpreterGroup, context.getAutheInfo());
+      Note note = notebook.createNote(noteName, defaultInterpreterGroup, context.getAutheInfo());
       note.addNewParagraph(context.getAutheInfo()); // it's an empty note. so add one paragraph
       note.setName(noteName);
       note.setCronSupported(notebook.getConf());
@@ -219,7 +220,8 @@ public class NotebookService {
                               String text,
                               Map<String, Object> params,
                               Map<String, Object> config,
-                              boolean isRunAll,
+                              boolean failIfDisabled,
+                              boolean blocking,
                               ServiceContext context,
                               ServiceCallback<Paragraph> callback) throws IOException {
 
@@ -237,10 +239,8 @@ public class NotebookService {
       callback.onFailure(new ParagraphNotFoundException(paragraphId), context);
       return false;
     }
-    if (!p.isEnabled()) {
-      if (!isRunAll) {
-        callback.onFailure(new IOException("paragraph is disabled."), context);
-      }
+    if (failIfDisabled && !p.isEnabled()) {
+      callback.onFailure(new IOException("paragraph is disabled."), context);
       return false;
     }
     p.setText(text);
@@ -260,7 +260,7 @@ public class NotebookService {
 
     try {
       note.persist(p.getAuthenticationInfo());
-      boolean result = note.run(p.getId(), false);
+      boolean result = note.run(p.getId(), blocking);
       callback.onSuccess(p, context);
       return result;
     } catch (Exception ex) {
@@ -297,7 +297,8 @@ public class NotebookService {
       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, true, context, callback)) {
+      if (runParagraph(noteId, paragraphId, title, text, params, config, false, true,
+          context, callback)) {
         // stop execution when one paragraph fails.
         break;
       }
@@ -735,7 +736,28 @@ public class NotebookService {
     }
   }
 
+  public void getEditorSetting(String noteId,
+                               String replName,
+                               ServiceContext context,
+                               ServiceCallback<Map<String, Object>> callback) throws IOException {
 
+    Note note = notebook.getNote(noteId);
+    if (note == null) {
+      callback.onFailure(new NoteNotFoundException(noteId), context);
+      return;
+    }
+    try {
+      Interpreter intp = notebook.getInterpreterFactory().getInterpreter(
+          context.getAutheInfo().getUser(), noteId, replName,
+          notebook.getNote(noteId).getDefaultInterpreterGroup());
+      Map<String, Object> settings = notebook.getInterpreterSettingManager().
+          getEditorSetting(intp, context.getAutheInfo().getUser(), noteId, replName);
+      callback.onSuccess(settings, context);
+    } catch (InterpreterNotFoundException e) {
+      callback.onFailure(new IOException("Fail to find interpreter", e), context);
+      return;
+    }
+  }
 
 
   enum Permission {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 a376623..9ffb8c2 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
@@ -23,6 +23,7 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObject;
@@ -32,9 +33,7 @@ import org.apache.zeppelin.display.GUI;
 import org.apache.zeppelin.display.Input;
 import org.apache.zeppelin.helium.ApplicationEventListener;
 import org.apache.zeppelin.helium.HeliumPackage;
-import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
-import org.apache.zeppelin.interpreter.InterpreterNotFoundException;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.interpreter.InterpreterResultMessage;
 import org.apache.zeppelin.interpreter.InterpreterSetting;
@@ -161,7 +160,7 @@ public class NotebookServer extends WebSocketServlet
     return ZeppelinServer.notebook;
   }
 
-  private synchronized NotebookService getNotebookService() {
+  public synchronized NotebookService getNotebookService() {
     if (this.notebookService == null) {
       this.notebookService = new NotebookService(notebook());
     }
@@ -254,7 +253,7 @@ public class NotebookServer extends WebSocketServlet
           listNotes(conn, messagereceived);
           break;
         case RELOAD_NOTES_FROM_REPO:
-          broadcastReloadedNoteList(subject, userAndRoles);
+          broadcastReloadedNoteList(conn, getServiceContext(messagereceived));
           break;
         case GET_HOME_NOTE:
           getHomeNote(conn, messagereceived);
@@ -747,16 +746,20 @@ public class NotebookServer extends WebSocketServlet
         });
   }
 
-  public void broadcastReloadedNoteList(AuthenticationInfo subject, HashSet userAndRoles) {
-    if (subject == null) {
-      subject = new AuthenticationInfo(StringUtils.EMPTY);
-    }
-
-    //reload and reply first to requesting user
-    List<Map<String, String>> notesInfo = generateNotesInfo(true, subject, userAndRoles);
-    multicastToUser(subject.getUser(), new Message(OP.NOTES_INFO).put("notes", notesInfo));
-    //to others afterwards
-    broadcastNoteListExcept(notesInfo, subject);
+  public void broadcastReloadedNoteList(NotebookSocket conn, ServiceContext context)
+      throws IOException {
+    getNotebookService().listNotes(false, context,
+        new WebSocketServiceCallback<List<Map<String, String>>>(conn) {
+          @Override
+          public void onSuccess(List<Map<String, String>> notesInfo,
+                                ServiceContext context) throws IOException {
+            super.onSuccess(notesInfo, context);
+            multicastToUser(context.getAutheInfo().getUser(),
+                new Message(OP.NOTES_INFO).put("notes", notesInfo));
+            //to others afterwards
+            broadcastNoteListExcept(notesInfo, context.getAutheInfo());
+          }
+        });
   }
 
   private void broadcastNoteListExcept(List<Map<String, String>> notesInfo,
@@ -979,9 +982,10 @@ public class NotebookServer extends WebSocketServlet
   private void createNote(NotebookSocket conn,
                           Message message) throws IOException {
 
-    String defaultInterpreterGroup = (String) message.get("defaultInterpreterGroup");
     String noteName = (String) message.get("name");
-    getNotebookService().createNote(defaultInterpreterGroup, noteName, getServiceContext(message),
+    String defaultInterpreterGroup = (String) message.get("defaultInterpreterGroup");
+
+    getNotebookService().createNote(noteName, defaultInterpreterGroup, getServiceContext(message),
         new WebSocketServiceCallback<Note>(conn) {
           @Override
           public void onSuccess(Note note, ServiceContext context) throws IOException {
@@ -995,8 +999,7 @@ public class NotebookServer extends WebSocketServlet
           public void onFailure(Exception ex, ServiceContext context) throws IOException {
             super.onFailure(ex, context);
             conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
-                "Oops! There is something wrong with the notebook file system. "
-                    + "Please check the logs for more details.")));
+                "Failed to create note.\n" + ExceptionUtils.getMessage(ex))));
           }
         });
   }
@@ -1004,9 +1007,6 @@ public class NotebookServer extends WebSocketServlet
   private void deleteNote(NotebookSocket conn,
                           Message fromMessage) throws IOException {
     String noteId = (String) fromMessage.get("id");
-    if (noteId == null) {
-      return;
-    }
     getNotebookService().removeNote(noteId, getServiceContext(fromMessage),
         new WebSocketServiceCallback<String>(conn) {
           @Override
@@ -1098,17 +1098,13 @@ public class NotebookServer extends WebSocketServlet
   private void restoreNote(NotebookSocket conn,
                            Message fromMessage) throws IOException {
     String noteId = (String) fromMessage.get("id");
-    if (noteId == null) {
-      return;
-    }
-
     getNotebookService().restoreNote(noteId, getServiceContext(fromMessage),
-        new WebSocketServiceCallback<Note>(conn));
+        new WebSocketServiceCallback(conn));
 
   }
 
   private void restoreFolder(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
-                             Message fromMessage) throws SchedulerException, IOException {
+                             Message fromMessage) throws IOException {
     String folderId = (String) fromMessage.get("id");
 
     if (folderId == null) {
@@ -1159,14 +1155,10 @@ public class NotebookServer extends WebSocketServlet
   private void updateParagraph(NotebookSocket conn,
                                Message fromMessage) throws IOException {
     String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      return;
-    }
     String noteId = getOpenNoteId(conn);
     if (noteId == null) {
       noteId = (String) fromMessage.get("noteId");
     }
-
     String title = (String) fromMessage.get("title");
     String text = (String) fromMessage.get("paragraph");
     Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
@@ -1177,6 +1169,7 @@ public class NotebookServer extends WebSocketServlet
         new WebSocketServiceCallback<Paragraph>(conn) {
           @Override
           public void onSuccess(Paragraph p, ServiceContext context) throws IOException {
+            super.onSuccess(p, context);
             if (p.getNote().isPersonalizedMode()) {
               Map<String, Paragraph> userParagraphMap =
                   p.getNote().getParagraph(paragraphId).getUserParagraphMap();
@@ -1246,7 +1239,6 @@ public class NotebookServer extends WebSocketServlet
 
   private void cloneNote(NotebookSocket conn,
                          Message fromMessage) throws IOException {
-
     String noteId = getOpenNoteId(conn);
     String name = (String) fromMessage.get("name");
     getNotebookService().cloneNote(noteId, name, getServiceContext(fromMessage),
@@ -1264,9 +1256,6 @@ public class NotebookServer extends WebSocketServlet
   private void clearAllParagraphOutput(NotebookSocket conn,
                                        Message fromMessage) throws IOException {
     final String noteId = (String) fromMessage.get("id");
-    if (StringUtils.isBlank(noteId)) {
-      return;
-    }
     getNotebookService().clearAllParagraphOutput(noteId, getServiceContext(fromMessage),
         new WebSocketServiceCallback<Note>(conn) {
           @Override
@@ -1301,9 +1290,6 @@ public class NotebookServer extends WebSocketServlet
   private void removeParagraph(NotebookSocket conn,
                                Message fromMessage) throws IOException {
     final String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      return;
-    }
     String noteId = getOpenNoteId(conn);
     getNotebookService().removeParagraph(noteId, paragraphId,
         getServiceContext(fromMessage), new WebSocketServiceCallback<Paragraph>(conn){
@@ -1319,9 +1305,6 @@ public class NotebookServer extends WebSocketServlet
   private void clearParagraphOutput(NotebookSocket conn,
                                     Message fromMessage) throws IOException {
     final String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      return;
-    }
     String noteId = getOpenNoteId(conn);
     getNotebookService().clearParagraphOutput(noteId, paragraphId, getServiceContext(fromMessage),
         new WebSocketServiceCallback<Paragraph>(conn) {
@@ -1585,10 +1568,6 @@ public class NotebookServer extends WebSocketServlet
   private void moveParagraph(NotebookSocket conn,
                              Message fromMessage) throws IOException {
     final String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      return;
-    }
-
     final int newIndex = (int) Double.parseDouble(fromMessage.get("index").toString());
     String noteId = getOpenNoteId(conn);
     getNotebookService().moveParagraph(noteId, paragraphId, newIndex,
@@ -1641,10 +1620,6 @@ public class NotebookServer extends WebSocketServlet
 
   private void cancelParagraph(NotebookSocket conn, Message fromMessage) throws IOException {
     final String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      return;
-    }
-
     String noteId = getOpenNoteId(conn);
     getNotebookService().cancelParagraph(noteId, paragraphId, getServiceContext(fromMessage),
         new WebSocketServiceCallback<>(conn));
@@ -1653,9 +1628,6 @@ public class NotebookServer extends WebSocketServlet
   private void runAllParagraphs(NotebookSocket conn,
                                 Message fromMessage) throws IOException {
     final String noteId = (String) fromMessage.get("noteId");
-    if (StringUtils.isBlank(noteId)) {
-      return;
-    }
     List<Map<String, Object>> paragraphs =
         gson.fromJson(String.valueOf(fromMessage.data.get("paragraphs")),
             new TypeToken<List<Map<String, Object>>>() {
@@ -1721,22 +1693,18 @@ public class NotebookServer extends WebSocketServlet
 
   private void runParagraph(NotebookSocket conn,
                             Message fromMessage) throws IOException {
-    final String paragraphId = (String) fromMessage.get("id");
-    if (paragraphId == null) {
-      //TODO(zjffdu) it is possible ?
-      return;
-    }
-
+    String paragraphId = (String) fromMessage.get("id");
     String noteId = getOpenNoteId(conn);
     String text = (String) fromMessage.get("paragraph");
     String title = (String) fromMessage.get("title");
     Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
     Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
-    getNotebookService().runParagraph(noteId, paragraphId, title, text, params, config, false,
-        getServiceContext(fromMessage),
+    getNotebookService().runParagraph(noteId, paragraphId, title, text, params, config,
+        false, false, getServiceContext(fromMessage),
         new WebSocketServiceCallback<Paragraph>(conn) {
           @Override
           public void onSuccess(Paragraph p, ServiceContext context) throws IOException {
+            super.onSuccess(p, context);
             if (p.getNote().isPersonalizedMode()) {
               Paragraph p2 = p.getNote().clearPersonalizedParagraphOutput(paragraphId,
                   context.getAutheInfo().getUser());
@@ -2140,17 +2108,6 @@ public class NotebookServer extends WebSocketServlet
           new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
     }
 
-    @Override
-    public void onUnbindInterpreter(Note note, InterpreterSetting setting) {
-//      Notebook notebook = notebookServer.notebook();
-//      List<Map<String, Object>> notebookJobs = notebook.getJobListByNoteId(note.getId());
-//      Map<String, Object> response = new HashMap<>();
-//      response.put("lastResponseUnixTime", System.currentTimeMillis());
-//      response.put("jobs", notebookJobs);
-//
-//      notebookServer.broadcast(JobManagerService.JOB_MANAGER_PAGE.getKey(),
-//          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
-    }
   }
 
   /**
@@ -2327,22 +2284,25 @@ public class NotebookServer extends WebSocketServlet
     String paragraphId = (String) fromMessage.get("paragraphId");
     String replName = (String) fromMessage.get("magic");
     String noteId = getOpenNoteId(conn);
-    String user = fromMessage.principal;
-    Message resp = new Message(OP.EDITOR_SETTING);
-    resp.put("paragraphId", paragraphId);
-    Interpreter interpreter;
 
-    try {
-      interpreter = notebook().getInterpreterFactory().getInterpreter(user, noteId, replName,
-          notebook().getNote(noteId).getDefaultInterpreterGroup());
-      LOG.debug("getEditorSetting for interpreter: {} for paragraph {}", replName, paragraphId);
-      resp.put("editor", notebook().getInterpreterSettingManager().
-          getEditorSetting(interpreter, user, noteId, replName));
-      conn.send(serializeMessage(resp));
-    } catch (InterpreterNotFoundException e) {
-      LOG.warn("Fail to get interpreter: " + replName);
-      return;
-    }
+    getNotebookService().getEditorSetting(noteId, replName,
+        getServiceContext(fromMessage),
+        new WebSocketServiceCallback<Map<String, Object>>(conn) {
+          @Override
+          public void onSuccess(Map<String, Object> settings,
+                                ServiceContext context) throws IOException {
+            super.onSuccess(settings, context);
+            Message resp = new Message(OP.EDITOR_SETTING);
+            resp.put("paragraphId", paragraphId);
+            resp.put("editor", settings);
+            conn.send(serializeMessage(resp));
+          }
+
+          @Override
+          public void onFailure(Exception ex, ServiceContext context) throws IOException {
+            LOG.warn(ex.getMessage());
+          }
+        });
   }
 
   private void getInterpreterSettings(NotebookSocket conn)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 f576c84..689b7af 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
@@ -129,6 +129,7 @@ public abstract class AbstractTestRestApi {
 
   protected static File zeppelinHome;
   protected static File confDir;
+  protected static File notebookDir;
 
   private String getUrl(String path) {
     String url;
@@ -187,6 +188,11 @@ public abstract class AbstractTestRestApi {
       System.setProperty(
           ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT.getVarName(),
           "spark");
+      notebookDir = new File(zeppelinHome.getAbsolutePath() + "/notebook_" + testClassName);
+      System.setProperty(
+          ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(),
+          notebookDir.getPath()
+      );
 
       // some test profile does not build zeppelin-web.
       // to prevent zeppelin starting up fail, create zeppelin-web/dist directory

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 62a3782..65280f8 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
@@ -199,11 +199,10 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     // This is partial test as newNote is in memory but is not persistent
     String newNoteName = newNote.getName();
     LOG.info("new note name is: " + newNoteName);
-    String expectedNoteName = noteName;
-    if (noteName.isEmpty()) {
-      expectedNoteName = "Note " + newNoteId;
+    if (StringUtils.isBlank(noteName)) {
+      noteName = "Untitled Note";
     }
-    assertEquals("compare note name", expectedNoteName, newNoteName);
+    assertEquals("compare note name", noteName, newNoteName);
     // cleanup
     ZeppelinServer.notebook.removeNote(newNoteId, anonymous);
     post.releaseConnection();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
new file mode 100644
index 0000000..cf3a67f
--- /dev/null
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.service;
+
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.rest.AbstractTestRestApi;
+import org.apache.zeppelin.server.ZeppelinServer;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+public class NotebookServiceTest extends AbstractTestRestApi {
+
+  private static NotebookService notebookService;
+
+  private ServiceContext context =
+      new ServiceContext(AuthenticationInfo.ANONYMOUS, new HashSet<>());
+
+  private ServiceCallback callback = mock(ServiceCallback.class);
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HELIUM_REGISTRY.getVarName(),
+        "helium");
+    AbstractTestRestApi.startUp(NotebookServiceTest.class.getSimpleName());
+    notebookService = ZeppelinServer.notebookWsServer.getNotebookService();
+  }
+
+  @AfterClass
+  public static void destroy() throws Exception {
+    AbstractTestRestApi.shutDown();
+  }
+
+  @Test
+  public void testNoteOperations() throws IOException {
+    // get home note
+    Note homeNote = notebookService.getHomeNote(context, callback);
+    assertNull(homeNote);
+    verify(callback).onSuccess(homeNote, context);
+
+    // create note
+    Note note1 = notebookService.createNote("note1", "test", context, callback);
+    assertEquals("note1", note1.getName());
+    assertEquals(1, note1.getParagraphCount());
+    verify(callback).onSuccess(note1, context);
+
+    // list note
+    reset(callback);
+    List<Map<String, String>> notesInfo = notebookService.listNotes(false, context, callback);
+    assertEquals(1, notesInfo.size());
+    assertEquals(note1.getId(), notesInfo.get(0).get("id"));
+    assertEquals(note1.getName(), notesInfo.get(0).get("name"));
+    verify(callback).onSuccess(notesInfo, context);
+
+    // get note
+    reset(callback);
+    Note note2 = notebookService.getNote(note1.getId(), context, callback);
+    assertEquals(note1, note2);
+    verify(callback).onSuccess(note2, context);
+
+    // rename note
+    reset(callback);
+    notebookService.renameNote(note1.getId(), "new_name", context, callback);
+    verify(callback).onSuccess(note1, context);
+    assertEquals("new_name", note1.getName());
+
+    // delete note
+    reset(callback);
+    notebookService.removeNote(note1.getId(), context, callback);
+    verify(callback).onSuccess("Delete note successfully", context);
+
+    // list note again
+    reset(callback);
+    notesInfo = notebookService.listNotes(false, context, callback);
+    assertEquals(0, notesInfo.size());
+    verify(callback).onSuccess(notesInfo, context);
+
+    // import note
+    reset(callback);
+    Note importedNote = notebookService.importNote("imported note", "{}", context, callback);
+    assertNotNull(importedNote);
+    verify(callback).onSuccess(importedNote, context);
+
+    // clone note
+    reset(callback);
+    Note clonedNote = notebookService.cloneNote(importedNote.getId(), "Cloned Note", context,
+        callback);
+    assertEquals(importedNote.getParagraphCount(), clonedNote.getParagraphCount());
+    verify(callback).onSuccess(clonedNote, context);
+  }
+
+  @Test
+  public void testParagraphOperations() throws IOException {
+    // create note
+    Note note1 = notebookService.createNote("note1", "python", context, callback);
+    assertEquals("note1", note1.getName());
+    assertEquals(1, note1.getParagraphCount());
+    verify(callback).onSuccess(note1, context);
+
+    // add paragraph
+    reset(callback);
+    Paragraph p = notebookService.insertParagraph(note1.getId(), 1, new HashMap<>(), context,
+        callback);
+    assertNotNull(p);
+    verify(callback).onSuccess(p, context);
+    assertEquals(2, note1.getParagraphCount());
+
+    // update paragraph
+    reset(callback);
+    notebookService.updateParagraph(note1.getId(), p.getId(), "my_title", "my_text",
+        new HashMap<>(), new HashMap<>(), context, callback);
+    assertEquals("my_title", p.getTitle());
+    assertEquals("my_text", p.getText());
+
+    // move paragraph
+    reset(callback);
+    notebookService.moveParagraph(note1.getId(), p.getId(), 0, context, callback);
+    assertEquals(p, note1.getParagraph(0));
+    verify(callback).onSuccess(p, context);
+
+    // run paragraph asynchronously
+    reset(callback);
+    boolean runStatus = notebookService.runParagraph(note1.getId(), p.getId(), "my_title", "1+1",
+        new HashMap<>(), new HashMap<>(), false, false, context, callback);
+    assertTrue(runStatus);
+    verify(callback).onSuccess(p, context);
+
+    // run paragraph synchronously via correct code
+    reset(callback);
+    runStatus = notebookService.runParagraph(note1.getId(), p.getId(), "my_title", "1+1",
+        new HashMap<>(), new HashMap<>(), false, true, context, callback);
+    assertTrue(runStatus);
+    verify(callback).onSuccess(p, context);
+
+    // run paragraph synchronously via invalid code
+    reset(callback);
+    runStatus = notebookService.runParagraph(note1.getId(), p.getId(), "my_title", "invalid_code",
+        new HashMap<>(), new HashMap<>(), false, true, context, callback);
+    assertFalse(runStatus);
+    // TODO(zjffdu) Enable it after ZEPPELIN-3699
+    // assertNotNull(p.getResult());
+    verify(callback).onSuccess(p, context);
+
+    // clean output
+    reset(callback);
+    notebookService.clearParagraphOutput(note1.getId(), p.getId(), context, callback);
+    assertNull(p.getResult());
+    verify(callback).onSuccess(p, context);
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumApplicationFactory.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumApplicationFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumApplicationFactory.java
index 50928bb..707a230 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumApplicationFactory.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/HeliumApplicationFactory.java
@@ -419,27 +419,6 @@ public class HeliumApplicationFactory implements ApplicationEventListener, Noteb
   }
 
   @Override
-  public void onUnbindInterpreter(Note note, InterpreterSetting setting) {
-    for (Paragraph p : note.getParagraphs()) {
-      Interpreter currentInterpreter = null;
-      try {
-        currentInterpreter = p.getBindedInterpreter();
-      } catch (InterpreterNotFoundException e) {
-        logger.warn("Not interpreter found", e);
-        return;
-      }
-      List<InterpreterInfo> infos = setting.getInterpreterInfos();
-      for (InterpreterInfo info : infos) {
-        if (currentInterpreter != null &&
-            info.getClassName().equals(currentInterpreter.getClassName())) {
-          onParagraphRemove(p);
-          break;
-        }
-      }
-    }
-  }
-
-  @Override
   public void onParagraphRemove(Paragraph paragraph) {
     List<ApplicationState> appStates = paragraph.getAllApplicationStates();
     for (ApplicationState app : appStates) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 6a23b64..cea5c28 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
@@ -119,9 +119,10 @@ public class Note implements ParagraphJobListener, JsonSerializable {
     generateId();
   }
 
-  public Note(String defaultInterpreterGroup, NotebookRepo repo, InterpreterFactory factory,
+  public Note(String name, String defaultInterpreterGroup, NotebookRepo repo, InterpreterFactory factory,
       InterpreterSettingManager interpreterSettingManager, JobListenerFactory jlFactory,
       SearchService noteIndex, Credentials credentials, NoteEventListener noteEventListener) {
+    this.name = name;
     this.defaultInterpreterGroup = defaultInterpreterGroup;
     this.repo = repo;
     this.factory = factory;
@@ -181,6 +182,10 @@ public class Note implements ParagraphJobListener, JsonSerializable {
     return defaultInterpreterGroup;
   }
 
+  public void setDefaultInterpreterGroup(String defaultInterpreterGroup) {
+    this.defaultInterpreterGroup = defaultInterpreterGroup;
+  }
+
   public Map<String, Object> getNoteParams() {
     return noteParams;
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 b1bff81..7632390 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
@@ -140,7 +140,7 @@ public class Notebook implements NoteEventListener {
    * @throws IOException
    */
   public Note createNote(AuthenticationInfo subject) throws IOException {
-    return createNote(interpreterSettingManager.getDefaultInterpreterSetting().getName(), subject);
+    return createNote("", interpreterSettingManager.getDefaultInterpreterSetting().getName(), subject);
   }
 
   /**
@@ -148,10 +148,10 @@ public class Notebook implements NoteEventListener {
    *
    * @throws IOException
    */
-  public Note createNote(String defaultInterpreterSetting, AuthenticationInfo subject)
+  public Note createNote(String name, String defaultInterpreterGroup, AuthenticationInfo subject)
       throws IOException {
     Note note =
-        new Note(defaultInterpreterSetting, notebookRepo, replFactory, interpreterSettingManager,
+        new Note(name, defaultInterpreterGroup, notebookRepo, replFactory, interpreterSettingManager,
             jobListenerFactory, noteSearchService, credentials, this);
     note.setNoteNameListener(folders);
 
@@ -497,6 +497,10 @@ public class Notebook implements NoteEventListener {
     note.setNotebookRepo(notebookRepo);
     note.setCronSupported(getConf());
 
+    if (note.getDefaultInterpreterGroup() == null) {
+      note.setDefaultInterpreterGroup(conf.getString(ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT));
+    }
+
     Map<String, SnapshotAngularObject> angularObjectSnapshot = new HashMap<>();
 
     // restore angular object --------------
@@ -1033,12 +1037,6 @@ public class Notebook implements NoteEventListener {
     }
   }
 
-  private void fireUnbindInterpreter(Note note, InterpreterSetting setting) {
-    for (NotebookEventListener listener : notebookEventListeners) {
-      listener.onUnbindInterpreter(note, setting);
-    }
-  }
-
   public Boolean isRevisionSupported() {
     if (notebookRepo instanceof NotebookRepoSync) {
       return ((NotebookRepoSync) notebookRepo).isRevisionSupportedInDefaultRepo();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookEventListener.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookEventListener.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookEventListener.java
index 904eba0..01ebec6 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookEventListener.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookEventListener.java
@@ -24,6 +24,4 @@ import org.apache.zeppelin.interpreter.InterpreterSetting;
 public interface NotebookEventListener extends NoteEventListener {
   public void onNoteRemove(Note note);
   public void onNoteCreate(Note note);
-
-  public void onUnbindInterpreter(Note note, InterpreterSetting setting);
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
index 23ebfaf..cd556a6 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
@@ -71,7 +71,7 @@ public abstract class AbstractInterpreterTest {
   }
 
   protected Note createNote() {
-    return new Note("test", null, interpreterFactory, interpreterSettingManager, null, null, null, null);
+    return new Note("test", "test", null, interpreterFactory, interpreterSettingManager, null, null, null, null);
   }
 
   protected InterpreterContext createDummyInterpreterContext() {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderTest.java
index cf01dff..0e86ad6 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderTest.java
@@ -71,13 +71,13 @@ public class FolderTest {
 
   @Before
   public void createFolderAndNotes() {
-    note1 = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    note1 = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note1.setName("this/is/a/folder/note1");
 
-    note2 = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    note2 = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note2.setName("this/is/a/folder/note2");
 
-    note3 = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    note3 = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note3.setName("this/is/a/folder/note3");
 
     folder = new Folder("this/is/a/folder");
@@ -118,7 +118,7 @@ public class FolderTest {
 
   @Test
   public void addNoteTest() {
-    Note note4 = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note4 = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note4.setName("this/is/a/folder/note4");
 
     folder.addNote(note4);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderViewTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderViewTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderViewTest.java
index ea49912..9521355 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderViewTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/FolderViewTest.java
@@ -89,7 +89,7 @@ public class FolderViewTest {
   Note abNote2;
 
   private Note createNote() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note.setNoteNameListener(folderView);
     return note;
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java
index cfdf0ac..41cf5df 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java
@@ -79,7 +79,7 @@ public class NoteTest {
     when(interpreter.getScheduler()).thenReturn(scheduler);
 
     String pText = "%spark sc.version";
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
 
     Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     p.setText(pText);
@@ -95,7 +95,7 @@ public class NoteTest {
 
   @Test
   public void addParagraphWithEmptyReplNameTest() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
 
     Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     assertNull(p.getText());
@@ -105,7 +105,7 @@ public class NoteTest {
   public void addParagraphWithLastReplNameTest() throws InterpreterNotFoundException {
     when(interpreterFactory.getInterpreter(anyString(), anyString(), eq("spark"), anyString())).thenReturn(interpreter);
 
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     p1.setText("%spark ");
     Paragraph p2 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
@@ -117,7 +117,7 @@ public class NoteTest {
   public void insertParagraphWithLastReplNameTest() throws InterpreterNotFoundException {
     when(interpreterFactory.getInterpreter(anyString(), anyString(), eq("spark"), anyString())).thenReturn(interpreter);
 
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     p1.setText("%spark ");
     Paragraph p2 = note.insertNewParagraph(note.getParagraphs().size(), AuthenticationInfo.ANONYMOUS);
@@ -129,7 +129,7 @@ public class NoteTest {
   public void insertParagraphWithInvalidReplNameTest() throws InterpreterNotFoundException {
     when(interpreterFactory.getInterpreter(anyString(), anyString(), eq("invalid"), anyString())).thenReturn(null);
 
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     p1.setText("%invalid ");
     Paragraph p2 = note.insertNewParagraph(note.getParagraphs().size(), AuthenticationInfo.ANONYMOUS);
@@ -139,7 +139,7 @@ public class NoteTest {
 
   @Test
   public void insertParagraphwithUser() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     Paragraph p = note.insertNewParagraph(note.getParagraphs().size(), AuthenticationInfo.ANONYMOUS);
     assertEquals("anonymous", p.getUser());
   }
@@ -149,7 +149,7 @@ public class NoteTest {
     when(interpreterFactory.getInterpreter(anyString(), anyString(), eq("md"), anyString())).thenReturn(interpreter);
     when(interpreter.getScheduler()).thenReturn(scheduler);
 
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     InterpreterResult result = new InterpreterResult(InterpreterResult.Code.SUCCESS, InterpreterResult.Type.TEXT, "result");
     p1.setResult(result);
@@ -165,7 +165,7 @@ public class NoteTest {
 
   @Test
   public void getFolderIdTest() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     // Ordinary case test
     note.setName("this/is/a/folder/noteName");
     assertEquals("this/is/a/folder", note.getFolderId());
@@ -181,7 +181,7 @@ public class NoteTest {
 
   @Test
   public void getNameWithoutPathTest() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     // Notes in the root folder
     note.setName("noteOnRootFolder");
     assertEquals("noteOnRootFolder", note.getNameWithoutPath());
@@ -196,7 +196,7 @@ public class NoteTest {
 
   @Test
   public void isTrashTest() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     // Notes in the root folder
     note.setName("noteOnRootFolder");
     assertFalse(note.isTrash());
@@ -219,15 +219,8 @@ public class NoteTest {
   }
 
   @Test
-  public void getNameWithoutNameItself() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
-
-    assertEquals("getName should return same as getId when name is empty", note.getId(), note.getName());
-  }
-
-  @Test
   public void personalizedModeReturnDifferentParagraphInstancePerUser() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
 
     String user1 = "user1";
     String user2 = "user2";
@@ -242,7 +235,7 @@ public class NoteTest {
   }
 
   public void testNoteJson() {
-    Note note = new Note("test", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
+    Note note = new Note("test", "", repo, interpreterFactory, interpreterSettingManager, jobListenerFactory, index, credentials, noteEventListener);
     note.setName("/test_note");
     note.getConfig().put("config_1", "value_1");
     note.getInfo().put("info_1", "value_1");

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/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 9b6af82..02a8992 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
@@ -1248,7 +1248,6 @@ public class NotebookTest extends AbstractInterpreterTest implements JobListener
     final AtomicInteger onNoteCreate = new AtomicInteger(0);
     final AtomicInteger onParagraphRemove = new AtomicInteger(0);
     final AtomicInteger onParagraphCreate = new AtomicInteger(0);
-    final AtomicInteger unbindInterpreter = new AtomicInteger(0);
 
     notebook.addNotebookEventListener(new NotebookEventListener() {
       @Override
@@ -1262,11 +1261,6 @@ public class NotebookTest extends AbstractInterpreterTest implements JobListener
       }
 
       @Override
-      public void onUnbindInterpreter(Note note, InterpreterSetting setting) {
-        unbindInterpreter.incrementAndGet();
-      }
-
-      @Override
       public void onParagraphRemove(Paragraph p) {
         onParagraphRemove.incrementAndGet();
       }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/09d44d50/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java
index f556b0d..52be4fa 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/search/LuceneSearchTest.java
@@ -300,6 +300,7 @@ public class LuceneSearchTest {
     Note note =
         new Note(
             "test",
+            "test",
             notebookRepoMock,
             interpreterFactory,
             interpreterSettingManager,