Repository: zeppelin
Updated Branches:
  refs/heads/master 0e087455a -> dd20e7bf8


[ZEPPELIN-1564] Enable note deletion and paragraph output clear from main page

### What is this PR for?
- Enables removing note and clear all paragraph's output from Zeppelin main 
page.
- Add rest api for clearing all paragraph output

Next possible improvement can be removing notes in folder level and rename 
folder.

### What type of PR is it?
Improvement

### Todos
* [x] - Merge #1567 and apply security to `clearAllParagraphOutput` rest api 
method

### What is the Jira issue?

[ZEPPELIN-1564](https://issues.apache.org/jira/browse/ZEPPELIN-1564)
### Screenshots (if appropriate)

![oct-27-2016 
18-26-03](https://cloud.githubusercontent.com/assets/8503346/19761938/e013ea02-9c72-11e6-9a08-0a70aca145d2.gif)
### Questions:
- Does the licenses files need update? no
- Is there breaking changes for older versions? no
- Does this needs documentation? yes

Author: Mina Lee <[email protected]>

Closes #1565 from minahlee/ZEPPELIN-1564 and squashes the following commits:

749aebe [Mina Lee] Merge branch 'master' of https://github.com/apache/zeppelin 
into ZEPPELIN-1564
1393ee9 [Mina Lee] Rename class name from UnauthorizedException to 
ForbiddenException Update clear output rest api doc response code
2ee452e [Mina Lee] Add auth check before clearing all paragraph
fb7e6ae [Mina Lee] Merge branch 'master' of https://github.com/apache/zeppelin 
into ZEPPELIN-1564
f349dbf [Mina Lee] Change post to put
7eb3521 [Mina Lee] Give writer permission to clear output
dea3ef6 [Mina Lee] Remove unused import
d66600c [Mina Lee] Add rest api endpoint for clear paragraph result to document
3d19141 [Mina Lee] Add rest api for clear all paragraph result and add test
98d7604 [Mina Lee] Add clearAllParagraphOutput unit test
4adddb4 [Mina Lee] Clear all paragraphs and remove note from main page


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

Branch: refs/heads/master
Commit: dd20e7bf8bf7b307f72b4f601fa675e06f7d03c5
Parents: 0e08745
Author: Mina Lee <[email protected]>
Authored: Sat Nov 5 12:49:36 2016 +0900
Committer: Mina Lee <[email protected]>
Committed: Sat Nov 5 13:41:11 2016 +0900

----------------------------------------------------------------------
 docs/rest-api/rest-notebook.md                  | 38 ++++++++++++++-
 .../apache/zeppelin/rest/NotebookRestApi.java   | 29 +++++++++--
 .../rest/exception/ForbiddenException.java      | 51 ++++++++++++++++++++
 .../rest/exception/UnauthorizedException.java   | 50 -------------------
 .../apache/zeppelin/socket/NotebookServer.java  | 22 +++++++++
 .../zeppelin/rest/AbstractTestRestApi.java      |  2 +-
 .../zeppelin/rest/NotebookRestApiTest.java      | 45 +++++++++++++++--
 .../rest/NotebookSecurityRestApiTest.java       |  8 +--
 zeppelin-web/src/app/home/home.controller.js    | 15 ++++--
 zeppelin-web/src/app/home/home.html             | 16 +++++-
 .../src/app/notebook/notebook-actionBar.html    |  2 +-
 .../src/app/notebook/notebook.controller.js     | 35 +++-----------
 .../components/noteAction/noteAction.service.js | 51 ++++++++++++++++++++
 .../websocketEvents/websocketMsg.service.js     |  4 ++
 zeppelin-web/src/index.html                     |  1 +
 .../java/org/apache/zeppelin/notebook/Note.java | 11 +++++
 .../zeppelin/notebook/socket/Message.java       |  5 +-
 .../org/apache/zeppelin/notebook/NoteTest.java  | 20 ++++++++
 18 files changed, 305 insertions(+), 100 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/docs/rest-api/rest-notebook.md
----------------------------------------------------------------------
diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md
index 1e49d1e..46f2cd1 100644
--- a/docs/rest-api/rest-notebook.md
+++ b/docs/rest-api/rest-notebook.md
@@ -493,7 +493,7 @@ If you work with Apache Zeppelin and find a need for an 
additional REST API, ple
     <col width="200">
     <tr>
       <td>Description</td>
-      <td> This ```POST``` method runs the paragraph synchronously by given 
note and paragraph id. This API can return SUCCESS or ERROR depending on the 
outcome of the paragraph execution
+      <td>This ```POST``` method runs the paragraph synchronously by given 
note and paragraph id. This API can return SUCCESS or ERROR depending on the 
outcome of the paragraph execution
       </td>
     </tr>
     <tr>
@@ -972,3 +972,39 @@ If you work with Apache Zeppelin and find a need for an 
additional REST API, ple
     </tr>
     </tr>
   </table>
+
+<br />
+### Clear all paragraph result
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <td>Description</td>
+      <td>This ```PUT``` method clear all paragraph results from note of given 
id.
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      
<td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/[noteId]/clear```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td>Forbidden code</td>
+      <td>401</td>
+    </tr>
+    <tr>
+      <td>Not Found code</td>
+      <td>404</td>
+    </tr>
+    <tr>
+      <td>Fail code</td>
+      <td>500</td>
+    </tr>
+    <tr>
+      <td>sample JSON response</td>
+      <td><pre>{"status": "OK"}</pre></td>
+    </tr>
+    </tr>
+  </table>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 5b27d0e..52f7d11 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
@@ -43,7 +43,7 @@ import org.apache.zeppelin.notebook.Notebook;
 import org.apache.zeppelin.notebook.NotebookAuthorization;
 import org.apache.zeppelin.notebook.Paragraph;
 import org.apache.zeppelin.rest.exception.NotFoundException;
-import org.apache.zeppelin.rest.exception.UnauthorizedException;
+import org.apache.zeppelin.rest.exception.ForbiddenException;
 import org.apache.zeppelin.rest.message.CronRequest;
 import org.apache.zeppelin.rest.message.NewNoteRequest;
 import org.apache.zeppelin.rest.message.NewParagraphRequest;
@@ -124,7 +124,7 @@ public class NotebookRestApi {
     userAndRoles.add(SecurityUtils.getPrincipal());
     userAndRoles.addAll(SecurityUtils.getRoles());
     if (!notebookAuthorization.isOwner(userAndRoles, noteId)) {
-      throw new UnauthorizedException(errorMsg);
+      throw new ForbiddenException(errorMsg);
     }
   }
   
@@ -136,7 +136,7 @@ public class NotebookRestApi {
     userAndRoles.add(SecurityUtils.getPrincipal());
     userAndRoles.addAll(SecurityUtils.getRoles());
     if (!notebookAuthorization.hasWriteAuthorization(userAndRoles, noteId)) {
-      throw new UnauthorizedException(errorMsg);
+      throw new ForbiddenException(errorMsg);
     }
   }
   
@@ -148,7 +148,7 @@ public class NotebookRestApi {
     userAndRoles.add(SecurityUtils.getPrincipal());
     userAndRoles.addAll(SecurityUtils.getRoles());
     if (!notebookAuthorization.hasReadAuthorization(userAndRoles, noteId)) {
-      throw new UnauthorizedException(errorMsg);
+      throw new ForbiddenException(errorMsg);
     }
   }
   
@@ -517,6 +517,27 @@ public class NotebookRestApi {
   }
 
   /**
+   * Clear result of all paragraphs REST API
+   *
+   * @param noteId ID of Note
+   * @return JSON with status.ok
+   */
+  @PUT
+  @Path("{noteId}/clear")
+  @ZeppelinApi
+  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();
+
+    return new JsonResponse(Status.OK, "").build();
+  }
+
+  /**
    * Run note jobs REST API
    *
    * @param noteId ID of Note

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java
new file mode 100644
index 0000000..04deb42
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.rest.exception;
+
+import static javax.ws.rs.core.Response.Status.FORBIDDEN;
+import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+import org.apache.zeppelin.utils.ExceptionUtils;
+
+/**
+ * UnauthorizedException handler for WebApplicationException.
+ * 
+ */
+public class ForbiddenException extends WebApplicationException {
+  private static final long serialVersionUID = 4394749068760407567L;
+  private static final String FORBIDDEN_MSG = "Not allowed to access";
+
+  public ForbiddenException() {
+    super(forbiddenJson(FORBIDDEN_MSG));
+  }
+
+  private static Response forbiddenJson(String message) {
+    return ExceptionUtils.jsonResponseContent(FORBIDDEN, message);
+  }
+  
+  public ForbiddenException(Throwable cause, String message) {
+    super(cause, forbiddenJson(message));
+  }
+  
+  public ForbiddenException(String message) {
+    super(forbiddenJson(message));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java
deleted file mode 100644
index 7b968ab..0000000
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.rest.exception;
-
-import static javax.ws.rs.core.Response.Status.FORBIDDEN;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
-
-import org.apache.zeppelin.utils.ExceptionUtils;
-
-/**
- * UnauthorizedException handler for WebApplicationException.
- * 
- */
-public class UnauthorizedException extends WebApplicationException {
-  private static final long serialVersionUID = 4394749068760407567L;
-  private static final String UNAUTHORIZED_MSG = "Authorization required";
-
-  public UnauthorizedException() {
-    super(unauthorizedJson(UNAUTHORIZED_MSG));
-  }
-
-  private static Response unauthorizedJson(String message) {
-    return ExceptionUtils.jsonResponseContent(FORBIDDEN, message);
-  }
-  
-  public UnauthorizedException(Throwable cause, String message) {
-    super(cause, unauthorizedJson(message));
-  }
-  
-  public UnauthorizedException(String message) {
-    super(unauthorizedJson(message));
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 3e137b8..6cba536 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
@@ -228,6 +228,9 @@ public class NotebookServer extends WebSocketServlet 
implements
           case PARAGRAPH_CLEAR_OUTPUT:
             clearParagraphOutput(conn, userAndRoles, notebook, 
messagereceived);
             break;
+          case PARAGRAPH_CLEAR_ALL_OUTPUT:
+            clearAllParagraphOutput(conn, userAndRoles, notebook, 
messagereceived);
+            break;
           case NOTE_UPDATE:
             updateNote(conn, userAndRoles, notebook, messagereceived);
             break;
@@ -822,6 +825,25 @@ public class NotebookServer extends WebSocketServlet 
implements
     broadcastNoteList(subject, userAndRoles);
   }
 
+  private void clearAllParagraphOutput(NotebookSocket conn, HashSet<String> 
userAndRoles,
+                                       Notebook notebook, Message fromMessage)
+      throws IOException {
+    final String noteId = (String) fromMessage.get("id");
+    if (StringUtils.isBlank(noteId)) {
+      return;
+    }
+    Note note = notebook.getNote(noteId);
+    NotebookAuthorization notebookAuthorization = 
notebook.getNotebookAuthorization();
+    if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
+      permissionError(conn, "clear output", fromMessage.principal,
+          userAndRoles, notebookAuthorization.getOwners(noteId));
+      return;
+    }
+
+    note.clearAllParagraphOutput();
+    broadcastNote(note);
+  }
+
   protected Note importNote(NotebookSocket conn, HashSet<String> userAndRoles,
                             Notebook notebook, Message fromMessage)
       throws IOException {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 6d10337..2ff8d40 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
@@ -539,7 +539,7 @@ public abstract class AbstractTestRestApi {
 
 
   /** Status code matcher */
-  protected Matcher<? super HttpMethodBase> isForbiden() { return 
responsesWith(403); }
+  protected Matcher<? super HttpMethodBase> isForbidden() { return 
responsesWith(403); }
 
   protected Matcher<? super HttpMethodBase> isAllowed() {
     return responsesWith(200);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
index 36b0f1c..15b6903 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
@@ -18,15 +18,14 @@
 package org.apache.zeppelin.rest;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PostMethod;
 import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.notebook.Note;
-import org.apache.zeppelin.notebook.NotebookAuthorization;
-import org.apache.zeppelin.notebook.NotebookAuthorizationInfoSaving;
+import org.apache.zeppelin.notebook.Paragraph;
 import org.apache.zeppelin.server.ZeppelinServer;
 import org.apache.zeppelin.user.AuthenticationInfo;
 import org.junit.AfterClass;
@@ -37,12 +36,11 @@ import org.junit.Test;
 import org.junit.runners.MethodSorters;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 
 /**
@@ -179,6 +177,43 @@ public class NotebookRestApiTest extends 
AbstractTestRestApi {
     ZeppelinServer.notebook.removeNote(clonedNoteId, anonymous);
 
   }
+
+  @Test
+  public void testClearAllParagraphOutput() throws IOException {
+    // Create note and set result explicitly
+    Note note = ZeppelinServer.notebook.createNote(anonymous);
+    Paragraph p1 = note.addParagraph();
+    InterpreterResult result = new 
InterpreterResult(InterpreterResult.Code.SUCCESS, InterpreterResult.Type.TEXT, 
"result");
+    p1.setResult(result);
+
+    Paragraph p2 = note.addParagraph();
+    p2.setReturn(result, new Throwable());
+
+    // clear paragraph result
+    PutMethod put = httpPut("/notebook/" + note.getId() + "/clear", "");
+    LOG.info("test clear paragraph output response\n" + 
put.getResponseBodyAsString());
+    assertThat(put, isAllowed());
+    put.releaseConnection();
+
+    // check if paragraph results are cleared
+    GetMethod get = httpGet("/notebook/" + note.getId() + "/paragraph/" + 
p1.getId());
+    assertThat(get, isAllowed());
+    Map<String, Object> resp1 = gson.fromJson(get.getResponseBodyAsString(), 
new TypeToken<Map<String, Object>>() {
+    }.getType());
+    Map<String, Object> resp1Body = (Map<String, Object>) resp1.get("body");
+    assertNull(resp1Body.get("result"));
+
+    get = httpGet("/notebook/" + note.getId() + "/paragraph/" + p2.getId());
+    assertThat(get, isAllowed());
+    Map<String, Object> resp2 = gson.fromJson(get.getResponseBodyAsString(), 
new TypeToken<Map<String, Object>>() {
+    }.getType());
+    Map<String, Object> resp2Body = (Map<String, Object>) resp2.get("body");
+    assertNull(resp2Body.get("result"));
+    get.releaseConnection();
+
+    //cleanup
+    ZeppelinServer.notebook.removeNote(note.getId(), anonymous);
+  }
 }
 
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
index 3c5978f..0c714fd 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java
@@ -82,10 +82,10 @@ public class NotebookSecurityRestApiTest extends 
AbstractTestRestApi {
     //set permission
     String payload = "{ \"owners\": [\"admin\"], \"readers\": [\"user2\"], 
\"writers\": [\"user2\"] }";
     PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , 
"admin", "password1");
-    assertThat("test set note premission method:", put, isAllowed());
+    assertThat("test set note permission method:", put, isAllowed());
     put.releaseConnection();
     
-    userTryGetNote(noteId, "user1", "password2", isForbiden());
+    userTryGetNote(noteId, "user1", "password2", isForbidden());
     
     userTryGetNote(noteId, "user2", "password3", isAllowed());
     
@@ -99,10 +99,10 @@ public class NotebookSecurityRestApiTest extends 
AbstractTestRestApi {
     //set permission
     String payload = "{ \"owners\": [\"admin\", \"user1\"], \"readers\": 
[\"user2\"], \"writers\": [\"user2\"] }";
     PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , 
"admin", "password1");
-    assertThat("test set note premission method:", put, isAllowed());
+    assertThat("test set note permission method:", put, isAllowed());
     put.releaseConnection();
     
-    userTryRemoveNote(noteId, "user2", "password3", isForbiden());
+    userTryRemoveNote(noteId, "user2", "password3", isForbidden());
     userTryRemoveNote(noteId, "user1", "password2", isAllowed());
     
     Note deletedNote = ZeppelinServer.notebook.getNote(noteId);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-web/src/app/home/home.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.controller.js 
b/zeppelin-web/src/app/home/home.controller.js
index 171a275..1d11c79 100644
--- a/zeppelin-web/src/app/home/home.controller.js
+++ b/zeppelin-web/src/app/home/home.controller.js
@@ -22,10 +22,12 @@
     'websocketMsgSrv',
     '$rootScope',
     'arrayOrderingSrv',
-    'ngToast'
+    'ngToast',
+    'noteActionSrv'
   ];
 
-  function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, 
arrayOrderingSrv, ngToast) {
+  function HomeCtrl($scope, noteListDataFactory, websocketMsgSrv, $rootScope, 
arrayOrderingSrv,
+                    ngToast, noteActionSrv) {
     ngToast.dismiss();
     var vm = this;
     vm.notes = noteListDataFactory;
@@ -85,6 +87,13 @@
         vm.notebookHome = false;
       }
     });
-  }
 
+    $scope.removeNote = function(noteId) {
+      noteActionSrv.removeNote(noteId, false);
+    };
+
+    $scope.clearAllParagraphOutput = function(noteId) {
+      noteActionSrv.clearAllParagraphOutput(noteId);
+    };
+  }
 })();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-web/src/app/home/home.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.html 
b/zeppelin-web/src/app/home/home.html
index 0f8e968..a8d5e56 100644
--- a/zeppelin-web/src/app/home/home.html
+++ b/zeppelin-web/src/app/home/home.html
@@ -13,10 +13,24 @@ limitations under the License.
 -->
 
 <script type="text/ng-template" id="notebook_folder_renderer.html">
-  <div ng-if="node.children == null">
+  <div ng-if="node.children == null"
+       ng-mouseenter="showButton=true"
+       ng-mouseleave="showButton=false">
     <a style="text-decoration: none;" href="#/notebook/{{node.id}}">
       <i style="font-size: 10px;" class="icon-doc"/> {{noteName(node)}}
     </a>
+    <a style="text-decoration: none;">
+      <i style="font-size: 13px; margin-left: 10px; cursor: pointer; 
text-decoration: none;"
+         class="fa fa-eraser" ng-show="showButton" 
ng-click="clearAllParagraphOutput(node.id)"
+         tooltip-placement="bottom" tooltip="Clear output">
+      </i>
+    </a>
+    <a style="text-decoration: none;">
+      <i style="font-size: 13px; margin-left: 2px; cursor: pointer; 
text-decoration: none;"
+         class="fa fa-trash-o" ng-show="showButton" 
ng-click="removeNote(node.id)"
+         tooltip-placement="bottom" tooltip="Remove note">
+      </i>
+    </a>
   </div>
   <div ng-if="node.children != null">
     <a style="text-decoration: none; cursor: pointer;" 
ng-click="toggleFolderNode(node)">

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 38406df..16f0e10 100644
--- a/zeppelin-web/src/app/notebook/notebook-actionBar.html
+++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html
@@ -43,7 +43,7 @@ limitations under the License.
       </button>
       <button type="button"
               class="btn btn-default btn-xs"
-              ng-click="clearAllParagraphOutput()"
+              ng-click="clearAllParagraphOutput(note.id)"
               ng-hide="viewOnly"
               ng-class="{'disabled':isNoteRunning()}"
               tooltip-placement="bottom" tooltip="Clear output">

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 dbb7b94..f308047 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -27,12 +27,13 @@
     'baseUrlSrv',
     '$timeout',
     'saveAsService',
-    'ngToast'
+    'ngToast',
+    'noteActionSrv'
   ];
 
   function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope,
                         $http, websocketMsgSrv, baseUrlSrv, $timeout, 
saveAsService,
-                        ngToast) {
+                        ngToast, noteActionSrv) {
 
     ngToast.dismiss();
 
@@ -143,20 +144,9 @@
       $scope.$broadcast('doubleClickParagraph', paragraphId);
     };
 
-    /** Remove the note and go back tot he main page */
-    /** TODO(anthony): In the nearly future, go back to the main page and 
telle to the dude that the note have been remove */
+    // Remove the note and go back to the main page
     $scope.removeNote = function(noteId) {
-      BootstrapDialog.confirm({
-        closable: true,
-        title: '',
-        message: 'Do you want to delete this note?',
-        callback: function(result) {
-          if (result) {
-            websocketMsgSrv.deleteNote(noteId);
-            $location.path('/');
-          }
-        }
-      });
+      noteActionSrv.removeNote(noteId, true);
     };
 
     //Export notebook
@@ -230,19 +220,8 @@
       }
     };
 
-    $scope.clearAllParagraphOutput = function() {
-      BootstrapDialog.confirm({
-        closable: true,
-        title: '',
-        message: 'Do you want to clear all output?',
-        callback: function(result) {
-          if (result) {
-            _.forEach($scope.note.paragraphs, function(n, key) {
-              angular.element('#' + n.id + 
'_paragraphColumn_main').scope().clearParagraphOutput();
-            });
-          }
-        }
-      });
+    $scope.clearAllParagraphOutput = function(noteId) {
+      noteActionSrv.clearAllParagraphOutput(noteId);
     };
 
     $scope.toggleAllEditor = function() {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-web/src/components/noteAction/noteAction.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/noteAction/noteAction.service.js 
b/zeppelin-web/src/components/noteAction/noteAction.service.js
new file mode 100644
index 0000000..33e5722
--- /dev/null
+++ b/zeppelin-web/src/components/noteAction/noteAction.service.js
@@ -0,0 +1,51 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+(function() {
+
+  angular.module('zeppelinWebApp').service('noteActionSrv', noteActionSrv);
+
+  noteActionSrv.$inject = ['websocketMsgSrv', '$location'];
+
+  function noteActionSrv(websocketMsgSrv, $location) {
+    this.removeNote = function(noteId, redirectToHome) {
+      BootstrapDialog.confirm({
+        closable: true,
+        title: '',
+        message: 'Do you want to delete this note?',
+        callback: function(result) {
+          if (result) {
+            websocketMsgSrv.deleteNote(noteId);
+            if (redirectToHome) {
+              $location.path('/');
+            }
+          }
+        }
+      });
+    };
+
+    this.clearAllParagraphOutput = function(noteId) {
+      BootstrapDialog.confirm({
+        closable: true,
+        title: '',
+        message: 'Do you want to clear all output?',
+        callback: function(result) {
+          if (result) {
+            websocketMsgSrv.clearAllParagraphOutput(noteId);
+          }
+        }
+      });
+    };
+  }
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 da75939..8c025cc 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -128,6 +128,10 @@
         websocketEvents.sendNewEvent({op: 'PARAGRAPH_CLEAR_OUTPUT', data: {id: 
paragraphId}});
       },
 
+      clearAllParagraphOutput: function(noteId) {
+        websocketEvents.sendNewEvent({op: 'PARAGRAPH_CLEAR_ALL_OUTPUT', data: 
{id: noteId}});
+      },
+
       completion: function(paragraphId, buf, cursor) {
         websocketEvents.sendNewEvent({
           op: 'COMPLETION',

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 4ffec03..0248c7c 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -185,6 +185,7 @@ limitations under the License.
     <script src="components/searchService/search.service.js"></script>
     <script src="components/login/login.controller.js"></script>
     <script 
src="components/elasticInputCtrl/elasticInput.controller.js"></script>
+    <script src="components/noteAction/noteAction.service.js"></script>
     <!-- endbuild -->
   </body>
 </html>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 7ad2697..66362bd 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
@@ -327,6 +327,17 @@ public class Note implements Serializable, 
ParagraphJobListener {
   }
 
   /**
+   * Clear all paragraph output of note
+   */
+  public void clearAllParagraphOutput() {
+    synchronized (paragraphs) {
+      for (Paragraph p : paragraphs) {
+        p.setReturn(null, null);
+      }
+    }
+  }
+
+  /**
    * Move paragraph into the new index (order from 0 ~ n-1).
    *
    * @param paragraphId ID of paragraph

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
index d678661..b4da1e1 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
@@ -48,7 +48,7 @@ public class Message {
                       // @param id note id
     CLONE_NOTE,       // [c-s] clone new notebook
                       // @param id id of note to clone
-                      // @param name name fpor the cloned note
+                      // @param name name for the cloned note
     IMPORT_NOTE,      // [c-s] import notebook
                       // @param object notebook
     NOTE_UPDATE,
@@ -96,7 +96,8 @@ public class Message {
                                   // @param notes serialized List<NoteInfo> 
object
 
     PARAGRAPH_REMOVE,
-    PARAGRAPH_CLEAR_OUTPUT,
+    PARAGRAPH_CLEAR_OUTPUT,       // [c-s] clear output of paragraph
+    PARAGRAPH_CLEAR_ALL_OUTPUT,   // [c-s] clear output of all paragraphs
     PARAGRAPH_APPEND_OUTPUT,      // [s-c] append output
     PARAGRAPH_UPDATE_OUTPUT,      // [s-c] update (replace) output
     PING,

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dd20e7bf/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 a077274..ed42144 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
@@ -19,6 +19,7 @@ package org.apache.zeppelin.notebook;
 
 import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
+import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.notebook.repo.NotebookRepo;
 import org.apache.zeppelin.scheduler.Scheduler;
 import org.apache.zeppelin.search.SearchService;
@@ -125,4 +126,23 @@ public class NoteTest {
     assertNull(p2.getText());
   }
 
+  @Test
+  public void clearAllParagraphOutputTest() {
+    when(interpreterFactory.getInterpreter(anyString(), anyString(), 
eq("md"))).thenReturn(interpreter);
+    when(interpreter.getScheduler()).thenReturn(scheduler);
+
+    Note note = new Note(repo, interpreterFactory, jobListenerFactory, index, 
credentials, noteEventListener);
+    Paragraph p1 = note.addParagraph();
+    InterpreterResult result = new 
InterpreterResult(InterpreterResult.Code.SUCCESS, InterpreterResult.Type.TEXT, 
"result");
+    p1.setResult(result);
+
+    Paragraph p2 = note.addParagraph();
+    p2.setReturn(result, new Throwable());
+
+    note.clearAllParagraphOutput();
+
+    assertNull(p1.getReturn());
+    assertNull(p2.getReturn());
+  }
+
 }

Reply via email to