diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 1e49d1ed912..46f2cd1dd69 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 Description - 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 + 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 @@ -972,3 +972,39 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple + +
+### Clear all paragraph result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DescriptionThis ```PUT``` method clear all paragraph results from note of given id. +
URL```http://[zeppelin-server]:[zeppelin-port]/api/notebook/[noteId]/clear```
Success code200
Forbidden code401
Not Found code404
Fail code500
sample JSON response
{"status": "OK"}
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 5b27d0e6842..52f7d116150 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.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 @@ private void checkIfUserIsOwner(String noteId, String errorMsg) { 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 @@ private void checkIfUserCanWrite(String noteId, String errorMsg) { 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 @@ private void checkIfUserCanRead(String noteId, String errorMsg) { userAndRoles.add(SecurityUtils.getPrincipal()); userAndRoles.addAll(SecurityUtils.getRoles()); if (!notebookAuthorization.hasReadAuthorization(userAndRoles, noteId)) { - throw new UnauthorizedException(errorMsg); + throw new ForbiddenException(errorMsg); } } @@ -516,6 +516,27 @@ public Response deleteParagraph(@PathParam("noteId") String noteId, return new JsonResponse(Status.OK, "").build(); } + /** + * 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 * 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/ForbiddenException.java similarity index 71% rename from zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java rename to zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java index 7b968abaddc..04deb4227b5 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/ForbiddenException.java @@ -17,6 +17,7 @@ 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; @@ -27,24 +28,24 @@ * UnauthorizedException handler for WebApplicationException. * */ -public class UnauthorizedException extends WebApplicationException { +public class ForbiddenException extends WebApplicationException { private static final long serialVersionUID = 4394749068760407567L; - private static final String UNAUTHORIZED_MSG = "Authorization required"; + private static final String FORBIDDEN_MSG = "Not allowed to access"; - public UnauthorizedException() { - super(unauthorizedJson(UNAUTHORIZED_MSG)); + public ForbiddenException() { + super(forbiddenJson(FORBIDDEN_MSG)); } - private static Response unauthorizedJson(String message) { + private static Response forbiddenJson(String message) { return ExceptionUtils.jsonResponseContent(FORBIDDEN, message); } - public UnauthorizedException(Throwable cause, String message) { - super(cause, unauthorizedJson(message)); + public ForbiddenException(Throwable cause, String message) { + super(cause, forbiddenJson(message)); } - public UnauthorizedException(String message) { - super(unauthorizedJson(message)); + public ForbiddenException(String message) { + super(forbiddenJson(message)); } } 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 3e137b8bc2d..6cba5367481 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 void onMessage(NotebookSocket conn, String msg) { 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 @@ private void cloneNote(NotebookSocket conn, HashSet userAndRoles, broadcastNoteList(subject, userAndRoles); } + private void clearAllParagraphOutput(NotebookSocket conn, HashSet 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 userAndRoles, Notebook notebook, Message fromMessage) throws IOException { 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 6d103372987..2ff8d40b3de 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 static void ps() { /** Status code matcher */ - protected Matcher isForbiden() { return responsesWith(403); } + protected Matcher isForbidden() { return responsesWith(403); } protected Matcher isAllowed() { return responsesWith(200); 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 36b0f1c97b3..15b69030ce1 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.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 void testCloneNote() throws IOException { 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 resp1 = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() { + }.getType()); + Map resp1Body = (Map) resp1.get("body"); + assertNull(resp1Body.get("result")); + + get = httpGet("/notebook/" + note.getId() + "/paragraph/" + p2.getId()); + assertThat(get, isAllowed()); + Map resp2 = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() { + }.getType()); + Map resp2Body = (Map) resp2.get("body"); + assertNull(resp2Body.get("result")); + get.releaseConnection(); + + //cleanup + ZeppelinServer.notebook.removeNote(note.getId(), anonymous); + } } 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 3c5978fd4d5..0c714fd4251 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 void testThatOtherUserCannotAccessNoteIfPermissionSet() throws IOExceptio //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 void testThatWriterCannotRemoveNote() throws IOException { //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); diff --git a/zeppelin-web/src/app/home/home.controller.js b/zeppelin-web/src/app/home/home.controller.js index 171a27585e8..1d11c79bfae 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); + }; + } })(); diff --git a/zeppelin-web/src/app/home/home.html b/zeppelin-web/src/app/home/home.html index 0f8e9681b8e..a8d5e56088d 100644 --- a/zeppelin-web/src/app/home/home.html +++ b/zeppelin-web/src/app/home/home.html @@ -13,10 +13,24 @@ --> + 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 7ad269701f5..66362bd7b99 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 @@ -326,6 +326,17 @@ public Paragraph clearParagraphOutput(String paragraphId) { return null; } + /** + * 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). * 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 d678661df74..b4da1e1c580 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 static enum OP { // @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 static enum OP { // @param notes serialized List 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, 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 a07727458f2..ed42144bf7a 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 @@ 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 void insertParagraphWithInvalidReplNameTest() { 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()); + } + }