Repository: incubator-zeppelin Updated Branches: refs/heads/master 61bc27996 -> 2714d28b5
Rest Apis to export/import with common code base 1) Rest apis to export notebook as JSON 2) Rest api to import notebook from JSON https://issues.apache.org/jira/browse/ZEPPELIN-590 Author: swakrish <[email protected]> Author: Ramaswamy Devarajan <[email protected]> Closes #614 from swakrish/master and squashes the following commits: 83e7a6d [swakrish] Merge pull request #7 from apache/master 042d9af [swakrish] Merge pull request #6 from apache/master f5b0805 [swakrish] Merge branch 'master' of [email protected]:swakrish/incubator-zeppelin.git 869b48f [swakrish] logging the error f8bf1f3 [swakrish] Merge pull request #5 from apache/master f8a992c [swakrish] Merge branch 'master' of [email protected]:swakrish/incubator-zeppelin.git 270e17b [swakrish] changed soureJSON to sourceJson 4cb69be [swakrish] Merge pull request #4 from apache/master e282958 [swakrish] Changed websocket import to use common code e928c14 [swakrish] changed doc to make it http post 4be62f6 [swakrish] Merge branch 'master' of [email protected]:swakrish/incubator-zeppelin.git db6a580 [swakrish] changed HTTP Put to POST in the docs ec14034 [swakrish] Merge pull request #3 from apache/master 630664c [swakrish] Merge pull request #2 from apache/master b080d7d [swakrish] Merge pull request #1 from apache/master db8b016 [Ramaswamy Devarajan] added note not found check for export 7351f31 [Ramaswamy Devarajan] Moved export/import methods to Notebook.java 6c19668 [Ramaswamy Devarajan] Changed http put to Http post for REST import ecb8f1e [Ramaswamy Devarajan] Formatting for google style 9b64a66 [Ramaswamy Devarajan] fixed alignments 0e94dce [Ramaswamy Devarajan] add documentation 3645354 [Ramaswamy Devarajan] Rest Apis to export/import Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/2714d28b Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/2714d28b Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/2714d28b Branch: refs/heads/master Commit: 2714d28b5acf5fd5dc0f0135a6fea7b88c189756 Parents: 61bc279 Author: swakrish <[email protected]> Authored: Thu Jan 21 16:16:14 2016 -0800 Committer: Alexander Bezzubov <[email protected]> Committed: Fri Jan 22 18:37:54 2016 +0900 ---------------------------------------------------------------------- docs/rest-api/rest-notebook.md | 111 ++++++++++++++++++- .../apache/zeppelin/rest/NotebookRestApi.java | 32 +++++- .../apache/zeppelin/socket/NotebookServer.java | 53 +-------- .../zeppelin/rest/ZeppelinRestApiTest.java | 81 ++++++++++++++ .../org/apache/zeppelin/notebook/Notebook.java | 56 ++++++++++ 5 files changed, 284 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/2714d28b/docs/rest-api/rest-notebook.md ---------------------------------------------------------------------- diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 3c94268..a1e8de6 100644 --- a/docs/rest-api/rest-notebook.md +++ b/docs/rest-api/rest-notebook.md @@ -33,7 +33,7 @@ limitations under the License. <br /> ### Notebook REST API list - Notebooks REST API supports the following operations: List, Create, Get, Delete, Clone, Run as detailed in the following table + Notebooks REST API supports the following operations: List, Create, Get, Delete, Clone, Run, Export, Import as detailed in the following table <table class="table-configuration"> <col width="200"> @@ -773,3 +773,112 @@ limitations under the License. </tr> </table> + + + <table class="table-configuration"> + <col width="200"> + <tr> + <th>Export notebook</th> + <th></th> + </tr> + <tr> + <td>Description</td> + <td>This ```GET``` method exports a notebook by the given id and gernerates a JSON + </td> + </tr> + <tr> + <td>URL</td> + <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/export/[notebookId]```</td> + </tr> + <tr> + <td>Success code</td> + <td>201</td> + </tr> + <tr> + <td> Fail code</td> + <td> 500 </td> + </tr> + <td> sample JSON response </td> + <td><pre>{ + "paragraphs": [ + { + "text": "%md This is my new paragraph in my new note", + "dateUpdated": "Jan 8, 2016 4:49:38 PM", + "config": { + "enabled": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "jobName": "paragraph_1452300578795_1196072540", + "id": "20160108-164938_1685162144", + "dateCreated": "Jan 8, 2016 4:49:38 PM", + "status": "READY", + "progressUpdateIntervalMs": 500 + } + ], + "name": "source note for export", + "id": "2B82H3RR1", + "angularObjects": {}, + "config": {}, + "info": {} +}</pre></td> + </tr> + </table> + + <table class="table-configuration"> + <col width="200"> + <tr> + <th>Export notebook</th> + <th></th> + </tr> + <tr> + <td>Description</td> + <td>This ```POST``` method imports a notebook from the notebook JSON input + </td> + </tr> + <tr> + <td>URL</td> + <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/import```</td> + </tr> + <tr> + <td>Success code</td> + <td>201</td> + </tr> + <tr> + <td> Fail code</td> + <td> 500 </td> + </tr> + <td> sample JSON input </td> + <td><pre>{ + "paragraphs": [ + { + "text": "%md This is my new paragraph in my new note", + "dateUpdated": "Jan 8, 2016 4:49:38 PM", + "config": { + "enabled": true + }, + "settings": { + "params": {}, + "forms": {} + }, + "jobName": "paragraph_1452300578795_1196072540", + "id": "20160108-164938_1685162144", + "dateCreated": "Jan 8, 2016 4:49:38 PM", + "status": "READY", + "progressUpdateIntervalMs": 500 + } + ], + "name": "source note for export", + "id": "2B82H3RR1", + "angularObjects": {}, + "config": {}, + "info": {} +}</pre></td> +<tr> + <td> sample JSON response </td> + <td><pre>"status": "CREATED","message": "","body": "2AZPHY918"}</pre></td> + </tr> + </tr> + </table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/2714d28b/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 7871da8..486e5b1 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 @@ -52,7 +52,9 @@ import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; - +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonReader; +import java.io.StringReader; /** * Rest api endpoint for the noteBook. */ @@ -147,6 +149,34 @@ public class NotebookRestApi { } /** + * export note REST API + * + * @param + * @return note JSON with status.OK + * @throws IOException + */ + @GET + @Path("export/{id}") + public Response exportNoteBook(@PathParam("id") String noteId) throws IOException { + String exportJson = notebook.exportNote(noteId); + return new JsonResponse(Status.OK, "", exportJson).build(); + } + + /** + * import new note REST API + * + * @param req - notebook Json + * @return JSON with new note ID + * @throws IOException + */ + @POST + @Path("import") + public Response importNotebook(String req) throws IOException { + Note newNote = notebook.importNote(req, null); + return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build(); + } + + /** * Create new note REST API * @param message - JSON with new note name * @return JSON with new note ID http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/2714d28b/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 64698fc..11fa7f1 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 @@ -494,56 +494,15 @@ public class NotebookServer extends WebSocketServlet implements protected Note importNote(NotebookSocket conn, Notebook notebook, Message fromMessage) throws IOException { - - Note note = notebook.createNote(); + Note note = null; if (fromMessage != null) { String noteName = (String) ((Map) fromMessage.get("notebook")).get("name"); - if (noteName == null || noteName.isEmpty()) { - noteName = "Note " + note.getId(); - } - note.setName(noteName); - ArrayList<Map> paragraphs = ((Map<String, ArrayList>) fromMessage.get("notebook")) - .get("paragraphs"); - if (paragraphs.size() > 0) { - for (Map paragraph : paragraphs) { - try { - Paragraph p = note.addParagraph(); - String text = (String) paragraph.get("text"); - p.setText(text); - p.setTitle((String) paragraph.get("title")); - Map<String, Object> params = (Map<String, Object>) ((Map) paragraph - .get("settings")).get("params"); - Map<String, Input> forms = (Map<String, Input>) ((Map) paragraph - .get("settings")).get("forms"); - if (params != null) { - p.settings.setParams(params); - } - if (forms != null) { - p.settings.setForms(forms); - } - Map<String, Object> result = (Map) paragraph.get("result"); - if (result != null) { - InterpreterResult.Code code = InterpreterResult.Code - .valueOf((String) result.get("code")); - InterpreterResult.Type type = InterpreterResult.Type - .valueOf((String) result.get("type")); - String msg = (String) result.get("msg"); - p.setReturn(new InterpreterResult(code, type, msg), null); - } - - Map<String, Object> config = (Map<String, Object>) paragraph - .get("config"); - p.setConfig(config); - } catch (Exception e) { - LOG.error("Exception while setting parameter in paragraph", e); - } - } - } + String noteJson = gson.toJson(fromMessage.get("notebook")); + note = notebook.importNote(noteJson, noteName); + note.persist(); + broadcastNote(note); + broadcastNoteList(); } - - note.persist(); - broadcastNote(note); - broadcastNoteList(); return note; } http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/2714d28b/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 c7ac631..d8049cc 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 @@ -319,6 +319,87 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { } + @Test + public void testExportNotebook() throws IOException { + LOG.info("testExportNotebook"); + Note note = ZeppelinServer.notebook.createNote(); + assertNotNull("can't create new note", note); + note.setName("source note for export"); + Paragraph paragraph = note.addParagraph(); + Map config = paragraph.getConfig(); + config.put("enabled", true); + paragraph.setConfig(config); + paragraph.setText("%md This is my new paragraph in my new note"); + note.persist(); + String sourceNoteID = note.getId(); + // Call export Notebook REST API + GetMethod get = httpGet("/notebook/export/" + sourceNoteID); + LOG.info("testNotebookExport \n" + get.getResponseBodyAsString()); + assertThat("test notebook export method:", get, isAllowed()); + + Map<String, Object> resp = + gson.fromJson(get.getResponseBodyAsString(), + new TypeToken<Map<String, Object>>() {}.getType()); + + String exportJSON = (String) resp.get("body"); + assertNotNull("Can not find new notejson", exportJSON); + LOG.info("export JSON:=" + exportJSON); + ZeppelinServer.notebook.removeNote(sourceNoteID); + get.releaseConnection(); + + } + + @Test + public void testImportNotebook() throws IOException { + Map<String, Object> resp; + String noteName = "source note for import"; + LOG.info("testImortNotebook"); + // create test notebook + Note note = ZeppelinServer.notebook.createNote(); + assertNotNull("can't create new note", note); + note.setName(noteName); + Paragraph paragraph = note.addParagraph(); + Map config = paragraph.getConfig(); + config.put("enabled", true); + paragraph.setConfig(config); + paragraph.setText("%md This is my new paragraph in my new note"); + note.persist(); + String sourceNoteID = note.getId(); + // get note content as JSON + String oldJson = getNoteContent(sourceNoteID); + // call notebook post + PostMethod importPost = httpPost("/notebook/import/", oldJson); + assertThat(importPost, isCreated()); + resp = + gson.fromJson(importPost.getResponseBodyAsString(), + new TypeToken<Map<String, Object>>() {}.getType()); + String importId = (String) resp.get("body"); + + assertNotNull("Did not get back a notebook id in body", importId); + Note newNote = ZeppelinServer.notebook.getNote(importId); + assertEquals("Compare note names", noteName, newNote.getName()); + assertEquals("Compare paragraphs count", note.getParagraphs().size(), newNote.getParagraphs() + .size()); + // cleanup + ZeppelinServer.notebook.removeNote(note.getId()); + ZeppelinServer.notebook.removeNote(newNote.getId()); + importPost.releaseConnection(); + } + + private String getNoteContent(String id) throws IOException { + GetMethod get = httpGet("/notebook/export/" + id); + assertThat(get, isAllowed()); + get.addRequestHeader("Origin", "http://localhost"); + Map<String, Object> resp = + gson.fromJson(get.getResponseBodyAsString(), + new TypeToken<Map<String, Object>>() {}.getType()); + assertEquals(200, get.getStatusCode()); + String body = resp.get("body").toString(); + // System.out.println("Body is " + body); + get.releaseConnection(); + return body; + } + private void testDeleteNotebook(String notebookId) throws IOException { DeleteMethod delete = httpDelete(("/notebook/" + notebookId)); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/2714d28b/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 a068cea..e58df0d 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 @@ -18,6 +18,7 @@ package org.apache.zeppelin.notebook; import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -54,6 +55,9 @@ import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonReader; /** * Collection of Notes. */ @@ -150,6 +154,58 @@ public class Notebook { note.persist(); return note; } + + /** + * Export existing note. + * @param noteId - the note ID to clone + * @return Note JSON + * @throws IOException, IllegalArgumentException + */ + public String exportNote(String noteId) throws IOException, IllegalArgumentException { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + Note note = getNote(noteId); + if (note == null) { + throw new IllegalArgumentException(noteId + " not found"); + } + return gson.toJson(note); + } + + /** + * import JSON as a new note. + * @param sourceJson - the note JSON to import + * @param noteName - the name of the new note + * @return notebook ID + * @throws IOException + */ + public Note importNote(String sourceJson, String noteName) throws IOException { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + JsonReader reader = new JsonReader(new StringReader(sourceJson)); + reader.setLenient(true); + Note newNote; + try { + Note oldNote = gson.fromJson(reader, Note.class); + newNote = createNote(); + if (noteName != null) + newNote.setName(noteName); + else + newNote.setName(oldNote.getName()); + List<Paragraph> paragraphs = oldNote.getParagraphs(); + for (Paragraph p : paragraphs) { + newNote.addCloneParagraph(p); + } + + newNote.persist(); + } catch (IOException e) { + logger.error(e.toString(), e); + throw e; + } + + return newNote; + } /** * Clone existing note.
