Repository: zeppelin Updated Branches: refs/heads/master 1f7366479 -> 89a1c53f2
[ZEPPELIN-2106] providing paragraph config in create note/paragraph rest call ### What is this PR for? * Allow to provide full paragraph config directly in the Create Paragraph and Create Note endpoint. * This saves some calls to [noteId]/paragraph/[paragraphId]/config * Updated doc. ### What type of PR is it? Improvement ### Todos ### What is the Jira issue? [ZEPPELIN-2106](https://issues.apache.org/jira/browse/ZEPPELIN-2106) ### How should this be tested? Outline the steps to test the PR here. 1. Clone the first paragraph of 'Zeppelin Tutorial/Basic Features (Spark)' to get the bank data loaded in a new note. 2. curl -X POST -d testAPI.json http://localhost:8080/api/notebook/$YOURNOTEID/paragraph 3. When running the paragraphes, the graphs will show up with the appropriate settings. testAPI.json: `{ "title":"Example providing config", "text":"%sql\nselect age, marital, count(1) cvalue from bank group by age, marital order by age", "config": { "title":true, "colWidth":6.0, "results": [ { "graph": { "mode": "scatterChart", "optionOpen": true } } ] }, "colWidth":9.0 } ` ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Remilito <remy.ga...@gmail.com> Closes #2099 from Remilito/ZEPPELIN-2106b and squashes the following commits: 0994ac0 [Remilito] [ZEPPELIN-2106]: keeping only the API providing the whole config 76af44a [Remilito] [ZEPPELIN-2106] providing paragraph config in create note/paragraph call * Allow to describe graph, colWidth, showTitle or full paragraph config directly in the Create Paragraph and Create Note endpoint. * Updated doc. Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/89a1c53f Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/89a1c53f Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/89a1c53f Branch: refs/heads/master Commit: 89a1c53f247688c1f521f8c2a8622a8484cb23cf Parents: 1f73664 Author: Remilito <remy.ga...@gmail.com> Authored: Mon Mar 20 12:41:09 2017 +0100 Committer: Lee moon soo <m...@apache.org> Committed: Tue Mar 21 11:13:46 2017 -0700 ---------------------------------------------------------------------- docs/rest-api/rest-notebook.md | 34 +++++++++++- .../apache/zeppelin/rest/NotebookRestApi.java | 58 ++++++++++++-------- .../rest/message/NewParagraphRequest.java | 8 +++ .../zeppelin/rest/ZeppelinRestApiTest.java | 41 ++++++++++++-- 4 files changed, 111 insertions(+), 30 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/89a1c53f/docs/rest-api/rest-notebook.md ---------------------------------------------------------------------- diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 5a09450..1c5ebde 100644 --- a/docs/rest-api/rest-notebook.md +++ b/docs/rest-api/rest-notebook.md @@ -111,7 +111,19 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete, }, { "title": "paragraph title2", - "text": "paragraph text2" + "text": "paragraph text2", + "config": { + "title": true, + "colWidth": 6.0, + "results": [ + { + "graph": { + "mode": "scatterChart", + "optionOpen": true + } + } + ] + } } ] }</pre></td> @@ -601,6 +613,26 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete, }</pre></td> </tr> <tr> + <td> sample JSON input (providing paragraph config) </td> + <td><pre> +{ + "title": "paragraph title2", + "text": "paragraph text2", + "config": { + "title": true, + "colWidth": 6.0, + "results": [ + { + "graph": { + "mode": "pieChart", + "optionOpen": true + } + } + ] + } +}</pre></td> + </tr> + <tr> <td> sample JSON response </td> <td><pre> { http://git-wip-us.apache.org/repos/asf/zeppelin/blob/89a1c53f/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 8292fd0..8c1075e 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 @@ -18,11 +18,7 @@ package org.apache.zeppelin.rest; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -337,16 +333,16 @@ public class NotebookRestApi { @Path("/") @ZeppelinApi public Response createNote(String message) throws IOException { + String user = SecurityUtils.getPrincipal(); LOG.info("Create new note by JSON {}", message); NewNoteRequest request = gson.fromJson(message, NewNoteRequest.class); - AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + AuthenticationInfo subject = new AuthenticationInfo(user); Note note = notebook.createNote(subject); List<NewParagraphRequest> initialParagraphs = request.getParagraphs(); if (initialParagraphs != null) { for (NewParagraphRequest paragraphRequest : initialParagraphs) { Paragraph p = note.addParagraph(subject); - p.setTitle(paragraphRequest.getTitle()); - p.setText(paragraphRequest.getText()); + initParagraph(p, paragraphRequest, user); } } note.addParagraph(subject); // add one paragraph to the last @@ -425,6 +421,7 @@ public class NotebookRestApi { @ZeppelinApi public Response insertParagraph(@PathParam("noteId") String noteId, String message) throws IOException { + String user = SecurityUtils.getPrincipal(); LOG.info("insert paragraph {} {}", noteId, message); Note note = notebook.getNote(noteId); @@ -432,7 +429,7 @@ public class NotebookRestApi { checkIfUserCanWrite(noteId, "Insufficient privileges you cannot add paragraph to this note"); NewParagraphRequest request = gson.fromJson(message, NewParagraphRequest.class); - AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + AuthenticationInfo subject = new AuthenticationInfo(user); Paragraph p; Double indexDouble = request.getIndex(); if (indexDouble == null) { @@ -440,9 +437,7 @@ public class NotebookRestApi { } else { p = note.insertParagraph(indexDouble.intValue(), subject); } - p.setTitle(request.getTitle()); - p.setText(request.getText()); - + initParagraph(p, request, user); note.persist(subject); notebookServer.broadcastNote(note); return new JsonResponse<>(Status.OK, "", p.getId()).build(); @@ -486,17 +481,7 @@ public class NotebookRestApi { checkIfParagraphIsNotNull(p); Map<String, Object> newConfig = gson.fromJson(message, HashMap.class); - if (newConfig == null || newConfig.isEmpty()) { - LOG.warn("{} is trying to update paragraph {} of note {} with empty config", - user, paragraphId, noteId); - throw new BadRequestException("paragraph config cannot be empty"); - } - Map<String, Object> origConfig = p.getConfig(); - for (String key : newConfig.keySet()) { - origConfig.put(key, newConfig.get(key)); - } - - p.setConfig(origConfig); + configureParagraph(p, newConfig, user); AuthenticationInfo subject = new AuthenticationInfo(user); note.persist(subject); @@ -963,4 +948,31 @@ public class NotebookRestApi { } } + private void initParagraph(Paragraph p, NewParagraphRequest request, String user) + throws IOException { + LOG.info("Init Paragraph for user {}", user); + checkIfParagraphIsNotNull(p); + p.setTitle(request.getTitle()); + p.setText(request.getText()); + Map< String, Object > config = request.getConfig(); + if ( config != null && !config.isEmpty()) { + configureParagraph(p, config, user); + } + } + + private void configureParagraph(Paragraph p, Map< String, Object> newConfig, String user) + throws IOException { + LOG.info("Configure Paragraph for user {}", user); + if (newConfig == null || newConfig.isEmpty()) { + LOG.warn("{} is trying to update paragraph {} of note {} with empty config", + user, p.getId(), p.getNote().getId()); + throw new BadRequestException("paragraph config cannot be empty"); + } + Map<String, Object> origConfig = p.getConfig(); + for (String key : newConfig.keySet()) { + origConfig.put(key, newConfig.get(key)); + } + p.setConfig(origConfig); + } + } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/89a1c53f/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewParagraphRequest.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewParagraphRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewParagraphRequest.java index bde920b..5be732f 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewParagraphRequest.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewParagraphRequest.java @@ -17,15 +17,21 @@ package org.apache.zeppelin.rest.message; +import java.util.HashMap; + /** * NewParagraphRequest rest api request message * * index field will be ignored when it's used to provide initial paragraphs + * visualization (optional) one of: + * table,pieChart,multibarChart,stackedAreaChart,lineChart,scatterChart + * colWidth (optional), e.g. 12.0 */ public class NewParagraphRequest { String title; String text; Double index; + HashMap< String, Object > config; public NewParagraphRequest() { @@ -42,4 +48,6 @@ public class NewParagraphRequest { public Double getIndex() { return index; } + + public HashMap< String, Object > getConfig() { return config; } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/89a1c53f/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 e7b95e4..d53c3b5 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 @@ -133,8 +133,11 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { String noteName = "test"; String jsonRequest = "{\"name\":\"" + noteName + "\", \"paragraphs\": [" + "{\"title\": \"title1\", \"text\": \"text1\"}," + - "{\"title\": \"title2\", \"text\": \"text2\"}" + - "]}"; + "{\"title\": \"title2\", \"text\": \"text2\"}," + + "{\"title\": \"titleConfig\", \"text\": \"text3\", " + + "\"config\": {\"colWidth\": 9.0, \"title\": true, "+ + "\"results\": [{\"graph\": {\"mode\": \"pieChart\"}}] "+ + "}}]} "; PostMethod post = httpPost("/notebook/", jsonRequest); LOG.info("testNoteCreate \n" + post.getResponseBodyAsString()); assertThat("test note create method:", post, isAllowed()); @@ -154,13 +157,20 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { expectedNoteName = "Note " + newNoteId; } assertEquals("compare note name", expectedNoteName, newNoteName); - assertEquals("initial paragraph check failed", 3, newNote.getParagraphs().size()); + assertEquals("initial paragraph check failed", 4, newNote.getParagraphs().size()); for (Paragraph p : newNote.getParagraphs()) { if (StringUtils.isEmpty(p.getText())) { continue; } assertTrue("paragraph title check failed", p.getTitle().startsWith("title")); assertTrue("paragraph text check failed", p.getText().startsWith("text")); + if ( p.getTitle() == "titleConfig"){ + assertEquals("paragraph col width check failed", 9.0, p.getConfig().get("colWidth")); + assertTrue("paragraph show title check failed", ((boolean) p.getConfig().get("title"))); + Map graph = ((List<Map>)p.getConfig().get("results")).get(0); + String mode = graph.get("mode").toString(); + assertEquals("paragraph graph mode check failed", "pieChart", mode); + } } // cleanup ZeppelinServer.notebook.removeNote(newNoteId, anonymous); @@ -213,8 +223,8 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { @Test - public void testexportNote() throws IOException { - LOG.info("testexportNote"); + public void testExportNote() throws IOException { + LOG.info("testExportNote"); Note note = ZeppelinServer.notebook.createNote(anonymous); assertNotNull("can't create new note", note); note.setName("source note for export"); @@ -246,7 +256,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { public void testImportNotebook() throws IOException { Map<String, Object> resp; String noteName = "source note for import"; - LOG.info("testImortNote"); + LOG.info("testImportNote"); // create test note Note note = ZeppelinServer.notebook.createNote(anonymous); assertNotNull("can't create new note", note); @@ -620,6 +630,25 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi { assertEquals("title2", paragraphAtIdx0.getTitle()); assertEquals("text2", paragraphAtIdx0.getText()); + //append paragraph providing graph + String jsonRequest3 = "{\"title\": \"title3\", \"text\": \"text3\", "+ + "\"config\": {\"colWidth\": 9.0, \"title\": true, "+ + "\"results\": [{\"graph\": {\"mode\": \"pieChart\"}}]}}"; + PostMethod post3 = httpPost("/notebook/" + note.getId() + "/paragraph", jsonRequest3); + LOG.info("testInsertParagraph response4\n" + post3.getResponseBodyAsString()); + assertThat("Test insert method:", post3, isAllowed()); + post3.releaseConnection(); + + Paragraph p = note.getLastParagraph(); + assertEquals("title3", p.getTitle()); + assertEquals("text3", p.getText()); + Map result = ((List<Map>)p.getConfig().get("results")).get(0); + String mode = ((Map)result.get("graph")).get("mode").toString(); + assertEquals("pieChart", mode); + assertEquals(9.0, p.getConfig().get("colWidth")); + assertTrue(((boolean) p.getConfig().get("title"))); + + ZeppelinServer.notebook.removeNote(note.getId(), anonymous); }