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)  ### 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()); + } + }
