You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by pd...@apache.org on 2020/10/21 08:46:43 UTC

[zeppelin] branch master updated: [ZEPPELIN-5098] Remove internal proprietary API in jupyter module

This is an automated email from the ASF dual-hosted git repository.

pdallig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new fee1e29  [ZEPPELIN-5098] Remove internal proprietary API in jupyter module
fee1e29 is described below

commit fee1e2934e97863b63953e471261b49c518be641
Author: Philipp Dallig <ph...@gmail.com>
AuthorDate: Mon Oct 12 11:53:31 2020 +0200

    [ZEPPELIN-5098] Remove internal proprietary API in jupyter module
    
    ### What is this PR for?
    This PR includes:
     - Use `java.util.regex.Pattern` instead of `com.sun.org.apache.xerces.internal.impl.xpath.regex.RegularExpression` which is an internal proprietary API
     - style changes in `zeppelin-jupyter/src/test/java/org/apache/zeppelin/jupyter/nbformat/JupyterUtilTest.java`
     - add a simple test for `getNbformat`
    
    ### What type of PR is it?
    - Refactoring
    
    ### What is the Jira issue?
    * https://issues.apache.org/jira/browse/ZEPPELIN-5098
    
    ### How should this be tested?
    * Travis-CI: https://travis-ci.org/github/Reamer/zeppelin/builds/734957587
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: Philipp Dallig <ph...@gmail.com>
    
    Closes #3944 from Reamer/Jupyter and squashes the following commits:
    
    1f1031072 [Philipp Dallig] use java.util.regex.Pattern instead of com.sun.org.apache.xerces.internal.impl.xpath.regex.RegularExpression
    aa22f677a [Philipp Dallig] some style changes
    4820e8bd3 [Philipp Dallig] some cleanup
---
 .../org/apache/zeppelin/jupyter/JupyterUtil.java   |  27 +-
 .../org/apache/zeppelin/jupyter/nbformat/Cell.java |   1 -
 .../zeppelin/jupyter/nbformat/ExecuteResult.java   |   1 -
 .../zeppelin/jupyter/nbformat/JupyterUtilTest.java |  54 ++-
 .../src/test/resources/spark_example_notebook.zpln | 456 +++++++++++++++++++++
 5 files changed, 505 insertions(+), 34 deletions(-)

diff --git a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/JupyterUtil.java b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/JupyterUtil.java
index 48e5319..cc6d1bd 100644
--- a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/JupyterUtil.java
+++ b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/JupyterUtil.java
@@ -21,7 +21,6 @@ import com.google.gson.GsonBuilder;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
-import com.sun.org.apache.xerces.internal.impl.xpath.regex.RegularExpression;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -57,13 +56,14 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  *
  */
 public class JupyterUtil {
 
-  private static Gson Pretty_Gson = new GsonBuilder().setPrettyPrinting().create();
+  private static final Gson PRETTY_GSON = new GsonBuilder().setPrettyPrinting().create();
 
   private final RuntimeTypeAdapterFactory<Cell> cellTypeFactory;
   private final RuntimeTypeAdapterFactory<Output> outputTypeFactory;
@@ -106,7 +106,7 @@ public class JupyterUtil {
       name = "Note converted from Jupyter_" + id;
     }
     note.setName(name);
-    
+
     String lineSeparator = System.lineSeparator();
     Paragraph paragraph;
     List<Paragraph> paragraphs = new ArrayList<>();
@@ -180,11 +180,11 @@ public class JupyterUtil {
     JsonObject nbformat = new JsonObject();
     JsonArray cells = new JsonArray();
 
-    RegularExpression MD = new RegularExpression("%md\\s");
-    RegularExpression SQL = new RegularExpression("%sql\\s");
-    RegularExpression UNKNOWN_MAGIC = new RegularExpression("%\\w+\\s");
-    RegularExpression HTML = new RegularExpression("%html\\s");
-    RegularExpression SPARK = new RegularExpression("%spark\\s");
+    Pattern mdPattern = Pattern.compile("%md\\s.*", Pattern.DOTALL);
+    Pattern sqlPattern = Pattern.compile("%sql\\s", Pattern.DOTALL);
+    Pattern unknownMagicPattern = Pattern.compile("%\\w+\\s", Pattern.DOTALL);
+    Pattern htmlPattern = Pattern.compile("%html\\s", Pattern.DOTALL);
+    Pattern sparkPattern = Pattern.compile("%spark\\s", Pattern.DOTALL);
 
     int index = 0;
     for (Paragraph paragraph : noteFormat.getParagraphs()) {
@@ -193,20 +193,19 @@ public class JupyterUtil {
 
       if (code == null || code.trim().isEmpty())
         continue;
-
-      if (MD.matches(code)) {
+      if (mdPattern.matcher(code).matches()) {
         codeJson.addProperty("cell_type", "markdown");
         codeJson.add("metadata", new JsonObject());
         codeJson.addProperty("source",
                 StringUtils.stripStart(StringUtils.stripStart(code, "%md"),
                         "\n"));  // remove '%md'
-      } else if (SQL.matches(code) || HTML.matches(code)) {
+      } else if (sqlPattern.matcher(code).matches() || htmlPattern.matcher(code).matches()) {
         codeJson.addProperty("cell_type", "code");
         codeJson.addProperty("execution_count", index);
         codeJson.add("metadata", new JsonObject());
         codeJson.add("outputs", new JsonArray());
         codeJson.addProperty("source", "%" + code);  // add % to convert to cell magic
-      } else if (SPARK.matches(code)) {
+      } else if (sparkPattern.matcher(code).matches()) {
         codeJson.addProperty("cell_type", "code");
         codeJson.addProperty("execution_count", index);
         JsonObject metadataJson = new JsonObject();
@@ -214,7 +213,7 @@ public class JupyterUtil {
         codeJson.add("metadata", metadataJson);
         codeJson.add("outputs", new JsonArray());
         codeJson.addProperty("source", code);
-      } else if (UNKNOWN_MAGIC.matches(code)) {
+      } else if (unknownMagicPattern.matcher(code).matches()) {
         // use raw cells for unknown magic
         codeJson.addProperty("cell_type", "raw");
         JsonObject metadataJson = new JsonObject();
@@ -257,7 +256,7 @@ public class JupyterUtil {
     nbformat.addProperty("nbformat", 4);
     nbformat.addProperty("nbformat_minor", 2);
     nbformat.add("cells", cells);
-    return Pretty_Gson.toJson(nbformat);
+    return PRETTY_GSON.toJson(nbformat);
   }
 
   public static void main(String[] args) throws ParseException, IOException {
diff --git a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/Cell.java b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/Cell.java
index f593da5..1d79f1f 100644
--- a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/Cell.java
+++ b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/Cell.java
@@ -17,7 +17,6 @@
 package org.apache.zeppelin.jupyter.nbformat;
 
 import com.google.gson.annotations.SerializedName;
-import java.util.List;
 
 /**
  *
diff --git a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/ExecuteResult.java b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/ExecuteResult.java
index 4dc7289..4858ee0 100644
--- a/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/ExecuteResult.java
+++ b/zeppelin-jupyter/src/main/java/org/apache/zeppelin/jupyter/nbformat/ExecuteResult.java
@@ -17,7 +17,6 @@
 package org.apache.zeppelin.jupyter.nbformat;
 
 import com.google.gson.annotations.SerializedName;
-import org.apache.zeppelin.jupyter.types.JupyterOutputType;
 import org.apache.zeppelin.jupyter.types.ZeppelinOutputType;
 import org.apache.zeppelin.jupyter.zformat.TypeData;
 
diff --git a/zeppelin-jupyter/src/test/java/org/apache/zeppelin/jupyter/nbformat/JupyterUtilTest.java b/zeppelin-jupyter/src/test/java/org/apache/zeppelin/jupyter/nbformat/JupyterUtilTest.java
index 276f59c..033aa0b 100644
--- a/zeppelin-jupyter/src/test/java/org/apache/zeppelin/jupyter/nbformat/JupyterUtilTest.java
+++ b/zeppelin-jupyter/src/test/java/org/apache/zeppelin/jupyter/nbformat/JupyterUtilTest.java
@@ -16,14 +16,20 @@
  */
 package org.apache.zeppelin.jupyter.nbformat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
-import com.google.gson.Gson;
 import org.apache.zeppelin.jupyter.JupyterUtil;
 import org.apache.zeppelin.jupyter.zformat.Note;
 import org.apache.zeppelin.jupyter.zformat.Paragraph;
@@ -49,6 +55,7 @@ public class JupyterUtilTest {
   public void getNote() throws Exception {
     InputStream resource = getClass().getResourceAsStream("/examples.ipynb");
     Note n = new JupyterUtil().getNote(new InputStreamReader(resource), "", "%python", "%md");
+    assertNotNull(n);
   }
 
   @Test
@@ -56,44 +63,55 @@ public class JupyterUtilTest {
     String noteName = "Note converted from Jupyter";
     InputStream resource = getClass().getResourceAsStream("/basic.ipynb");
     Note n = new JupyterUtil().getNote(new InputStreamReader(resource), "", "%python", "%md");
-    Gson gson = new Gson();
-    System.out.println(gson.toJson(n));
-    System.out.println(n.getParagraphs().size());
-    assertTrue(n.getParagraphs().size() == 8);
+    assertEquals(8, n.getParagraphs().size());
     assertTrue(n.getName().startsWith(noteName));
 
     Paragraph firstParagraph = n.getParagraphs().get(0);
-    assertTrue(firstParagraph.getText().equals("%python\nimport numpy as np"));
-    assertTrue(firstParagraph.getStatus().equals("FINISHED"));
+    assertEquals("%python\nimport numpy as np", firstParagraph.getText());
+    assertEquals("FINISHED", firstParagraph.getStatus());
     Map<String, Object> config = firstParagraph.getConfig();
 
-    assertTrue(((String) config.get("editorMode")).equals("ace/mode/python"));
-    assertTrue(((boolean) config.get("editorHide")) == false);
+    assertEquals("ace/mode/python", config.get("editorMode"));
+    assertFalse((boolean) config.get("editorHide"));
 
     Paragraph markdownParagraph = n.getParagraphs().get(6);
 
-    assertTrue(markdownParagraph.getText().equals("%md\n" +
+    assertEquals("%md\n" +
             "<div class=\"alert\" style=\"border: 1px solid #aaa; background: radial-gradient(ellipse at center, #ffffff 50%, #eee 100%);\">\n" +
             "<div class=\"row\">\n" +
             "    <div class=\"col-sm-1\"><img src=\"https://knowledgeanyhow.org/static/images/favicon_32x32.png\" style=\"margin-top: -6px\"/></div>\n" +
             "    <div class=\"col-sm-11\">This notebook was created using <a href=\"https://knowledgeanyhow.org\">IBM Knowledge Anyhow Workbench</a>.  To learn more, visit us at <a href=\"https://knowledgeanyhow.org\">https://knowledgeanyhow.org</a>.</div>\n" +
             "    </div>\n" +
-            "</div>"));
-    assertTrue(markdownParagraph.getStatus().equals("FINISHED"));
+            "</div>", markdownParagraph.getText());
+    assertEquals("FINISHED", markdownParagraph.getStatus());
 
     Map<String, Object> markdownConfig = markdownParagraph.getConfig();
-    assertTrue(((String) markdownConfig.get("editorMode")).equals("ace/mode/markdown"));
-    assertTrue(((boolean) markdownConfig.get("editorHide")) == true);
-    assertTrue(markdownParagraph.getResults().getCode().equals("SUCCESS"));
+    assertEquals("ace/mode/markdown", markdownConfig.get("editorMode"));
+    assertTrue((boolean) markdownConfig.get("editorHide"));
+    assertEquals("SUCCESS", markdownParagraph.getResults().getCode());
     List<TypeData> results = markdownParagraph.getResults().getMsg();
-    assertTrue(results.get(0).getData().equals("<div class=\"markdown-body\">\n" +
+    assertEquals("<div class=\"markdown-body\">\n" +
             "<div class=\"alert\" style=\"border: 1px solid #aaa; background: radial-gradient(ellipse at center, #ffffff 50%, #eee 100%);\">\n" +
             "<div class=\"row\">\n" +
             "    <div class=\"col-sm-1\"><img src=\"https://knowledgeanyhow.org/static/images/favicon_32x32.png\" style=\"margin-top: -6px\"/></div>\n" +
             "    <div class=\"col-sm-11\">This notebook was created using <a href=\"https://knowledgeanyhow.org\">IBM Knowledge Anyhow Workbench</a>.  To learn more, visit us at <a href=\"https://knowledgeanyhow.org\">https://knowledgeanyhow.org</a>.</div>\n" +
             "    </div>\n" +
             "</div>\n" +
-            "</div>"));
-    assertTrue(results.get(0).getType().equals("HTML"));
+            "</div>" , results.get(0).getData());
+    assertEquals("HTML", results.get(0).getType());
+  }
+
+  @Test
+  public void testgetNbformat() {
+    InputStream resource = getClass().getResourceAsStream("/spark_example_notebook.zpln");
+    String text = new BufferedReader(
+      new InputStreamReader(resource, StandardCharsets.UTF_8))
+        .lines()
+        .collect(Collectors.joining("\n"));
+    JupyterUtil util = new JupyterUtil();
+    Nbformat nbformat = util.getNbformat(new StringReader(util.getNbformat(text)));
+    assertEquals(7 , nbformat.getCells().size());
+    assertEquals(3 , nbformat.getCells().stream().filter(c -> c instanceof MarkdownCell).count());
+    assertEquals(4 , nbformat.getCells().stream().filter(c -> c instanceof CodeCell).count());
   }
 }
diff --git a/zeppelin-jupyter/src/test/resources/spark_example_notebook.zpln b/zeppelin-jupyter/src/test/resources/spark_example_notebook.zpln
new file mode 100644
index 0000000..b801f1d
--- /dev/null
+++ b/zeppelin-jupyter/src/test/resources/spark_example_notebook.zpln
@@ -0,0 +1,456 @@
+{
+  "paragraphs": [
+    {
+      "text": "%md\n## Welcome to Zeppelin.\n##### This is a live tutorial, you can run the code yourself. (Shift-Enter to Run)",
+      "user": "anonymous",
+      "dateUpdated": "2016-12-17 15:32:15.000",
+      "config": {
+        "colWidth": 12.0,
+        "editorHide": true,
+        "results": [
+          {
+            "graph": {
+              "mode": "table",
+              "height": 300.0,
+              "optionOpen": false,
+              "keys": [],
+              "values": [],
+              "groups": [],
+              "scatter": {}
+            }
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true
+        },
+        "editorMode": "ace/mode/markdown",
+        "tableHide": false
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch2\u003eWelcome to Zeppelin.\u003c/h2\u003e\n\u003ch5\u003eThis is a live tutorial, you can run the code yourself. (Shift-Enter to Run)\u003c/h5\u003e\n\u003c/div\u003e"
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423836981412_-1007008116",
+      "id": "20150213-231621_168813393",
+      "dateCreated": "2015-02-13 23:16:21.000",
+      "dateStarted": "2016-12-17 15:32:15.000",
+      "dateFinished": "2016-12-17 15:32:18.000",
+      "status": "FINISHED"
+    },
+    {
+      "title": "Load data into table",
+      "text": "import org.apache.commons.io.IOUtils\nimport java.net.URL\nimport java.nio.charset.Charset\n\n// Zeppelin creates and injects sc (SparkContext) and sqlContext (HiveContext or SqlContext)\n// So you don\u0027t need create them manually\n\n// load bank data\nval bankText \u003d sc.parallelize(\n    IOUtils.toString(\n        new URL(\"https://s3.amazonaws.com/apache-zeppelin/tutorial/bank/bank.csv\"),\n        Charset.forName(\"utf8\")).split(\"\\n\"))\n\ncase class Bank(age [...]
+      "user": "anonymous",
+      "dateUpdated": "2020-05-08 11:18:36.766",
+      "config": {
+        "colWidth": 12.0,
+        "title": true,
+        "enabled": true,
+        "editorMode": "ace/mode/scala",
+        "results": [
+          {
+            "graph": {
+              "mode": "table",
+              "height": 300.0,
+              "optionOpen": false
+            }
+          }
+        ],
+        "editorSetting": {
+          "language": "scala",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        },
+        "fontSize": 9.0
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TEXT",
+            "data": "\u001b[33mwarning: \u001b[0mthere was one deprecation warning; re-run with -deprecation for details\nimport sqlContext.implicits._\nimport org.apache.commons.io.IOUtils\nimport java.net.URL\nimport java.nio.charset.Charset\n\u001b[1m\u001b[34mbankText\u001b[0m: \u001b[1m\u001b[32morg.apache.spark.rdd.RDD[String]\u001b[0m \u003d ParallelCollectionRDD[0] at parallelize at \u003cconsole\u003e:24\ndefined class Bank\n\u001b[1m\u001b[34mbank\u001b[0m: \u001b[1m\u001b[32mo [...]
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423500779206_-1502780787",
+      "id": "20150210-015259_1403135953",
+      "dateCreated": "2015-02-10 01:52:59.000",
+      "dateStarted": "2020-05-08 11:18:36.791",
+      "dateFinished": "2020-05-08 11:19:58.268",
+      "status": "FINISHED"
+    },
+    {
+      "text": "%sql \nselect age, count(1) value\nfrom bank \nwhere age \u003c 30 \ngroup by age \norder by age",
+      "user": "anonymous",
+      "dateUpdated": "2020-05-04 23:34:43.954",
+      "config": {
+        "colWidth": 4.0,
+        "results": [
+          {
+            "graph": {
+              "mode": "multiBarChart",
+              "height": 366.0,
+              "optionOpen": false,
+              "setting": {
+                "multiBarChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                }
+              },
+              "commonSetting": {},
+              "keys": [
+                {
+                  "name": "age",
+                  "index": 0.0,
+                  "aggr": "sum"
+                }
+              ],
+              "groups": [],
+              "values": [
+                {
+                  "name": "value",
+                  "index": 1.0,
+                  "aggr": "sum"
+                }
+              ]
+            },
+            "helium": {}
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        },
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9.0
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TABLE",
+            "data": "age\tvalue\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n"
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423500782552_-1439281894",
+      "id": "20150210-015302_1492795503",
+      "dateCreated": "2015-02-10 01:53:02.000",
+      "dateStarted": "2020-05-04 23:34:43.959",
+      "dateFinished": "2020-05-04 23:34:52.126",
+      "status": "FINISHED"
+    },
+    {
+      "text": "%sql \nselect age, count(1) value \nfrom bank \nwhere age \u003c ${maxAge\u003d30} \ngroup by age \norder by age",
+      "user": "anonymous",
+      "dateUpdated": "2020-05-04 23:34:45.514",
+      "config": {
+        "colWidth": 4.0,
+        "results": [
+          {
+            "graph": {
+              "mode": "multiBarChart",
+              "height": 294.0,
+              "optionOpen": false,
+              "setting": {
+                "multiBarChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                }
+              },
+              "commonSetting": {},
+              "keys": [
+                {
+                  "name": "age",
+                  "index": 0.0,
+                  "aggr": "sum"
+                }
+              ],
+              "groups": [],
+              "values": [
+                {
+                  "name": "value",
+                  "index": 1.0,
+                  "aggr": "sum"
+                }
+              ]
+            },
+            "helium": {}
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        },
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9.0
+      },
+      "settings": {
+        "params": {
+          "maxAge": "35"
+        },
+        "forms": {
+          "maxAge": {
+            "type": "TextBox",
+            "name": "maxAge",
+            "defaultValue": "30",
+            "hidden": false
+          }
+        }
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423720444030_-1424110477",
+      "id": "20150212-145404_867439529",
+      "dateCreated": "2015-02-12 14:54:04.000",
+      "dateStarted": "2020-05-04 23:34:45.520",
+      "dateFinished": "2020-05-04 23:34:54.074",
+      "status": "FINISHED"
+    },
+    {
+      "text": "%sql \nselect age, count(1) value \nfrom bank \nwhere marital\u003d\"${marital\u003dsingle,single|divorced|married}\" \ngroup by age \norder by age",
+      "user": "anonymous",
+      "dateUpdated": "2020-05-04 23:34:47.079",
+      "config": {
+        "colWidth": 4.0,
+        "results": [
+          {
+            "graph": {
+              "mode": "stackedAreaChart",
+              "height": 280.0,
+              "optionOpen": false,
+              "setting": {
+                "stackedAreaChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                }
+              },
+              "commonSetting": {},
+              "keys": [
+                {
+                  "name": "age",
+                  "index": 0.0,
+                  "aggr": "sum"
+                }
+              ],
+              "groups": [],
+              "values": [
+                {
+                  "name": "value",
+                  "index": 1.0,
+                  "aggr": "sum"
+                }
+              ]
+            },
+            "helium": {}
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        },
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9.0,
+        "runOnSelectionChange": true
+      },
+      "settings": {
+        "params": {
+          "marital": "single"
+        },
+        "forms": {
+          "marital": {
+            "type": "Select",
+            "options": [
+              {
+                "value": "single"
+              },
+              {
+                "value": "divorced"
+              },
+              {
+                "value": "married"
+              }
+            ],
+            "name": "marital",
+            "defaultValue": "single",
+            "hidden": false
+          }
+        }
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TABLE",
+            "data": "age\tvalue\n19\t4\n20\t3\n21\t7\n22\t9\n23\t17\n24\t13\n25\t33\n26\t56\n27\t64\n28\t78\n29\t56\n30\t92\n31\t86\n32\t105\n33\t61\n34\t75\n35\t46\n36\t50\n37\t43\n38\t44\n39\t30\n40\t25\n41\t19\n42\t23\n43\t21\n44\t20\n45\t15\n46\t14\n47\t12\n48\t12\n49\t11\n50\t8\n51\t6\n52\t9\n53\t4\n55\t3\n56\t3\n57\t2\n58\t7\n59\t2\n60\t5\n66\t2\n69\t1\n"
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423836262027_-210588283",
+      "id": "20150213-230422_1600658137",
+      "dateCreated": "2015-02-13 23:04:22.000",
+      "dateStarted": "2020-05-04 23:34:52.255",
+      "dateFinished": "2020-05-04 23:34:55.739",
+      "status": "FINISHED"
+    },
+    {
+      "text": "%md\n## Congratulations, it\u0027s done.\n##### You can create your own notebook in \u0027Notebook\u0027 menu. Good luck!",
+      "user": "anonymous",
+      "dateUpdated": "2016-12-17 15:30:24.000",
+      "config": {
+        "colWidth": 12.0,
+        "editorHide": true,
+        "results": [
+          {
+            "graph": {
+              "mode": "table",
+              "height": 300.0,
+              "optionOpen": false
+            }
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true
+        },
+        "editorMode": "ace/mode/markdown",
+        "tableHide": false
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003ch2\u003eCongratulations, it\u0026rsquo;s done.\u003c/h2\u003e\n\u003ch5\u003eYou can create your own notebook in \u0026lsquo;Notebook\u0026rsquo; menu. Good luck!\u003c/h5\u003e\n\u003c/div\u003e"
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1423836268492_216498320",
+      "id": "20150213-230428_1231780373",
+      "dateCreated": "2015-02-13 23:04:28.000",
+      "dateStarted": "2016-12-17 15:30:24.000",
+      "dateFinished": "2016-12-17 15:30:29.000",
+      "status": "FINISHED"
+    },
+    {
+      "text": "%md\n\nAbout bank data\n\n```\nCitation Request:\n  This dataset is public available for research. The details are described in [Moro et al., 2011]. \n  Please include this citation if you plan to use this database:\n\n  [Moro et al., 2011] S. Moro, R. Laureano and P. Cortez. Using Data Mining for Bank Direct Marketing: An Application of the CRISP-DM Methodology. \n  In P. Novais et al. (Eds.), Proceedings of the European Simulation and Modelling Conference - ESM\u00272011 [...]
+      "user": "anonymous",
+      "dateUpdated": "2016-12-17 15:30:34.000",
+      "config": {
+        "colWidth": 12.0,
+        "editorHide": true,
+        "results": [
+          {
+            "graph": {
+              "mode": "table",
+              "height": 300.0,
+              "optionOpen": false
+            }
+          }
+        ],
+        "enabled": true,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true
+        },
+        "editorMode": "ace/mode/markdown",
+        "tableHide": false
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "\u003cdiv class\u003d\"markdown-body\"\u003e\n\u003cp\u003eAbout bank data\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eCitation Request:\n  This dataset is public available for research. The details are described in [Moro et al., 2011]. \n  Please include this citation if you plan to use this database:\n\n  [Moro et al., 2011] S. Moro, R. Laureano and P. Cortez. Using Data Mining for Bank Direct Marketing: An Application of the CRISP-DM Methodology. \n  In P. Novai [...]
+          }
+        ]
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1427420818407_872443482",
+      "id": "20150326-214658_12335843",
+      "dateCreated": "2015-03-26 21:46:58.000",
+      "dateStarted": "2016-12-17 15:30:34.000",
+      "dateFinished": "2016-12-17 15:30:34.000",
+      "status": "FINISHED"
+    },
+    {
+      "config": {},
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "apps": [],
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1435955447812_-158639899",
+      "id": "20150703-133047_853701097",
+      "dateCreated": "2015-07-03 13:30:47.000",
+      "status": "READY"
+    }
+  ],
+  "name": "2. Spark Basic Features",
+  "id": "2A94M5J1Z",
+  "defaultInterpreterGroup": "spark",
+  "noteParams": {},
+  "noteForms": {},
+  "angularObjects": {},
+  "config": {
+    "looknfeel": "default",
+    "isZeppelinNotebookCronEnable": false
+  },
+  "info": {}
+}
\ No newline at end of file