You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by zj...@apache.org on 2017/04/20 02:57:01 UTC
zeppelin git commit: ZEPPELIN-2395. Refactor Input.java to make
dynamic forms extensible
Repository: zeppelin
Updated Branches:
refs/heads/master 4b1b521fc -> 7b585c739
ZEPPELIN-2395. Refactor Input.java to make dynamic forms extensible
### What is this PR for?
Currently, zeppelin only support 3 kinds of dynamic form controls: TextBox, Select, CheckBox. All the things are in `Input.java`, this is hard to add new controls, this PR is for refactoring Input to make dynamic forms extensible. Main Changes:
* Make `Input` as the base class of dynamic forms also use it as the factory class
* All the concret dynamic forms extend `Input`
* Add method `toJson` and `fromJson` for `GUI` for `GUI`'s serialization/deserialization. I plan to do it for other classes as well, so that we can remove duplicated serde code and also make it easy to test serialization/deserialization
* Change `z.input` to `z.textbox` as I think z.input is a little misleading. But I still keep `z.input` and make `z.input` as deprecated.
* Ideally the new input forms' json should be the same as the old input form json. But there's one bug in the old input form, `type` is missing if the input forms are created in frontend for textbox and select. So I keep the old input forms for compatibility. I will load the old input forms json and convert it into new input forms, and after saving, `note.json` would have the new input forms json.
After this PR, user needs to do 3 things to add new ui controls
* Implement its UI control classes, (refer TextBox/CheckBox/Select), and specify it in `TypeAdapterFactory` of `Input` for serde.
* Add parsing logic in `Input.getInputForm` if you want to support this control in frontend.
* Add display logic in `paragraph-parameterizedQueryForm.html`
### What type of PR is it?
[ Improvement | Refactoring]
### Todos
* [ ] - Task
### What is the Jira issue?
* https://issues.apache.org/jira/browse/ZEPPELIN-2395
### How should this be tested?
Test is added
### Screenshots (if appropriate)
### Questions:
* Does the licenses files need update? Yes
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: Jeff Zhang <zj...@apache.org>
Closes #2245 from zjffdu/ZEPPELIN-2395 and squashes the following commits:
16d42a8 [Jeff Zhang] ZEPPELIN-2395. Refactor Input.java to make dynamic forms extensible
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/7b585c73
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/7b585c73
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/7b585c73
Branch: refs/heads/master
Commit: 7b585c7399f29492c308f83e342cac29b0c7ca07
Parents: 4b1b521
Author: Jeff Zhang <zj...@apache.org>
Authored: Wed Apr 12 10:50:16 2017 +0800
Committer: Jeff Zhang <zj...@apache.org>
Committed: Thu Apr 20 10:56:54 2017 +0800
----------------------------------------------------------------------
LICENSE | 1 +
.../zeppelin/cassandra/InterpreterLogic.scala | 2 +-
.../cassandra/InterpreterLogicTest.java | 2 +-
.../org/apache/zeppelin/groovy/GObject.java | 2 +-
pom.xml | 7 +
.../main/resources/python/zeppelin_python.py | 2 +-
.../apache/zeppelin/spark/ZeppelinContext.java | 25 ++-
zeppelin-interpreter/pom.xml | 5 +
.../java/org/apache/zeppelin/display/GUI.java | 69 ++++++-
.../java/org/apache/zeppelin/display/Input.java | 178 +++++++------------
.../org/apache/zeppelin/display/OldInput.java | 87 +++++++++
.../display/RuntimeTypeAdapterFactory.java | 149 ++++++++++++++++
.../apache/zeppelin/display/ui/CheckBox.java | 45 +++++
.../apache/zeppelin/display/ui/OptionInput.java | 85 +++++++++
.../org/apache/zeppelin/display/ui/Select.java | 36 ++++
.../org/apache/zeppelin/display/ui/TextBox.java | 38 ++++
.../interpreter/remote/RemoteInterpreter.java | 4 +-
.../remote/RemoteInterpreterServer.java | 4 +-
.../org/apache/zeppelin/display/GUITest.java | 120 +++++++++++++
.../org/apache/zeppelin/display/InputTest.java | 35 ++--
.../apache/zeppelin/socket/NotebookServer.java | 5 +-
.../integration/ParagraphActionsIT.java | 2 +-
.../zeppelin/rest/ZeppelinSparkClusterTest.java | 2 +-
.../paragraph-parameterizedQueryForm.html | 20 +--
.../websocketEvents/websocketEvents.factory.js | 1 +
.../java/org/apache/zeppelin/notebook/Note.java | 19 ++
.../notebook/repo/AzureNotebookRepo.java | 2 +-
.../zeppelin/notebook/repo/S3NotebookRepo.java | 2 +-
.../zeppelin/notebook/repo/VFSNotebookRepo.java | 2 +-
29 files changed, 793 insertions(+), 158 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 83a9131..e206a6c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -255,6 +255,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
(Apache 2.0) Bootstrap v3.0.2 (http://getbootstrap.com/) - https://github.com/twbs/bootstrap/blob/v3.0.2/LICENSE
(Apache 2.0) Software under ./bigquery/* was developed at Google (http://www.google.com/). Licensed under the Apache v2.0 License.
(Apache 2.0) Roboto Font (https://github.com/google/roboto/)
+ (Apache 2.0) Gson extra (https://github.com/DanySK/gson-extras)
========================================================================
BSD 3-Clause licenses
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/cassandra/src/main/scala/org/apache/zeppelin/cassandra/InterpreterLogic.scala
----------------------------------------------------------------------
diff --git a/cassandra/src/main/scala/org/apache/zeppelin/cassandra/InterpreterLogic.scala b/cassandra/src/main/scala/org/apache/zeppelin/cassandra/InterpreterLogic.scala
index 363da7b..c83a186 100644
--- a/cassandra/src/main/scala/org/apache/zeppelin/cassandra/InterpreterLogic.scala
+++ b/cassandra/src/main/scala/org/apache/zeppelin/cassandra/InterpreterLogic.scala
@@ -30,7 +30,7 @@ import com.datastax.driver.core.exceptions.DriverException
import com.datastax.driver.core.policies.{LoggingRetryPolicy, FallthroughRetryPolicy, DowngradingConsistencyRetryPolicy, Policies}
import org.apache.zeppelin.cassandra.TextBlockHierarchy._
import org.apache.zeppelin.display.AngularObjectRegistry
-import org.apache.zeppelin.display.Input.ParamOption
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption
import org.apache.zeppelin.interpreter.InterpreterResult.Code
import org.apache.zeppelin.interpreter.{InterpreterException, InterpreterResult, InterpreterContext}
import org.slf4j.LoggerFactory
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/cassandra/src/test/java/org/apache/zeppelin/cassandra/InterpreterLogicTest.java
----------------------------------------------------------------------
diff --git a/cassandra/src/test/java/org/apache/zeppelin/cassandra/InterpreterLogicTest.java b/cassandra/src/test/java/org/apache/zeppelin/cassandra/InterpreterLogicTest.java
index 698397a..f3848fd 100644
--- a/cassandra/src/test/java/org/apache/zeppelin/cassandra/InterpreterLogicTest.java
+++ b/cassandra/src/test/java/org/apache/zeppelin/cassandra/InterpreterLogicTest.java
@@ -34,7 +34,7 @@ import com.datastax.driver.core.Statement;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.GUI;
-import org.apache.zeppelin.display.Input.ParamOption;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.junit.Rule;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
----------------------------------------------------------------------
diff --git a/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java b/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
index e460651..7f6809a 100644
--- a/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
+++ b/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
@@ -36,7 +36,7 @@ import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.GUI;
-import org.apache.zeppelin.display.Input.ParamOption;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.interpreter.RemoteWorksController;
import org.apache.zeppelin.interpreter.InterpreterException;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index e1a2094..3bede62 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,7 @@
<log4j.version>1.2.17</log4j.version>
<libthrift.version>0.9.2</libthrift.version>
<gson.version>2.2</gson.version>
+ <gson-extras.version>0.2.1</gson-extras.version>
<guava.version>15.0</guava.version>
<jetty.version>9.2.15.v20160210</jetty.version>
<httpcomponents.core.version>4.3.3</httpcomponents.core.version>
@@ -193,6 +194,12 @@
</dependency>
<dependency>
+ <groupId>org.danilopianini</groupId>
+ <artifactId>gson-extras</artifactId>
+ <version>${gson-extras.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${commons.configuration.version}</version>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/python/src/main/resources/python/zeppelin_python.py
----------------------------------------------------------------------
diff --git a/python/src/main/resources/python/zeppelin_python.py b/python/src/main/resources/python/zeppelin_python.py
index 31b993d..eff8824 100644
--- a/python/src/main/resources/python/zeppelin_python.py
+++ b/python/src/main/resources/python/zeppelin_python.py
@@ -53,7 +53,7 @@ class PyZeppelinContext(object):
def __init__(self, z):
self.z = z
- self.paramOption = gateway.jvm.org.apache.zeppelin.display.Input.ParamOption
+ self.paramOption = gateway.jvm.org.apache.zeppelin.display.ui.OptionInput.ParamOption
self.javaList = gateway.jvm.java.util.ArrayList
self.max_result = 1000
self._displayhook = lambda *args: None
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
----------------------------------------------------------------------
diff --git a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
index 7e1ab70..b78410f 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
@@ -40,7 +40,7 @@ import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectWatcher;
import org.apache.zeppelin.display.GUI;
-import org.apache.zeppelin.display.Input.ParamOption;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
@@ -114,14 +114,33 @@ public class ZeppelinContext {
public SQLContext sqlContext;
private GUI gui;
+ /**
+ * @deprecated use z.textbox instead
+ *
+ */
+ @Deprecated
@ZeppelinApi
public Object input(String name) {
- return input(name, "");
+ return textbox(name);
}
+ /**
+ * @deprecated use z.textbox instead
+ */
+ @Deprecated
@ZeppelinApi
public Object input(String name, Object defaultValue) {
- return gui.input(name, defaultValue);
+ return textbox(name, defaultValue.toString());
+ }
+
+ @ZeppelinApi
+ public Object textbox(String name) {
+ return textbox(name, "");
+ }
+
+ @ZeppelinApi
+ public Object textbox(String name, String defaultValue) {
+ return gui.textbox(name, defaultValue);
}
@ZeppelinApi
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml
index e55144c..109099c 100644
--- a/zeppelin-interpreter/pom.xml
+++ b/zeppelin-interpreter/pom.xml
@@ -62,6 +62,11 @@
</dependency>
<dependency>
+ <groupId>org.danilopianini</groupId>
+ <artifactId>gson-extras</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${commons.exec.version}</version>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/GUI.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/GUI.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/GUI.java
index 40ce8ca..66b21c6 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/GUI.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/GUI.java
@@ -17,17 +17,27 @@
package org.apache.zeppelin.display;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.zeppelin.display.ui.CheckBox;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
+import org.apache.zeppelin.display.ui.Select;
+import org.apache.zeppelin.display.ui.TextBox;
+
import java.io.Serializable;
import java.util.*;
-import org.apache.zeppelin.display.Input.ParamOption;
/**
* Settings of a form.
*/
public class GUI implements Serializable {
+ private static Gson gson = new GsonBuilder()
+ .registerTypeAdapterFactory(Input.TypeAdapterFactory)
+ .create();
+
Map<String, Object> params = new HashMap<>(); // form parameters from client
LinkedHashMap<String, Input> forms = new LinkedHashMap<>(); // form configuration
@@ -51,19 +61,29 @@ public class GUI implements Serializable {
this.forms = forms;
}
+ @Deprecated
+ public Object input(String id) {
+ return textbox(id, "");
+ }
+
+ @Deprecated
public Object input(String id, Object defaultValue) {
+ return textbox(id, defaultValue.toString());
+ }
+
+ public Object textbox(String id, String defaultValue) {
// first find values from client and then use default
Object value = params.get(id);
if (value == null) {
value = defaultValue;
}
- forms.put(id, new Input(id, defaultValue, "input"));
+ forms.put(id, new TextBox(id, defaultValue));
return value;
}
- public Object input(String id) {
- return input(id, "");
+ public Object textbox(String id) {
+ return textbox(id, "");
}
public Object select(String id, Object defaultValue, ParamOption[] options) {
@@ -71,7 +91,7 @@ public class GUI implements Serializable {
if (value == null) {
value = defaultValue;
}
- forms.put(id, new Input(id, defaultValue, "select", options));
+ forms.put(id, new Select(id, defaultValue, options));
return value;
}
@@ -81,7 +101,7 @@ public class GUI implements Serializable {
if (checked == null) {
checked = defaultChecked;
}
- forms.put(id, new Input(id, defaultChecked, "checkbox", options));
+ forms.put(id, new CheckBox(id, defaultChecked, options));
List<Object> filtered = new LinkedList<>();
for (Object o : checked) {
if (isValidOption(o, options)) {
@@ -103,4 +123,41 @@ public class GUI implements Serializable {
public void clear() {
this.forms = new LinkedHashMap<>();
}
+
+ public String toJson() {
+ return gson.toJson(this);
+ }
+
+ public void convertOldInput() {
+ for (Map.Entry<String, Input> entry : forms.entrySet()) {
+ if (entry.getValue() instanceof OldInput) {
+ Input convertedInput = convertFromOldInput((OldInput) entry.getValue());
+ forms.put(entry.getKey(), convertedInput);
+ }
+ }
+ }
+
+ public static GUI fromJson(String json) {
+ GUI gui = gson.fromJson(json, GUI.class);
+ gui.convertOldInput();
+ return gui;
+ }
+
+ private Input convertFromOldInput(OldInput oldInput) {
+ Input convertedInput = null;
+
+ if (oldInput.options == null || oldInput instanceof OldInput.OldTextBox) {
+ convertedInput = new TextBox(oldInput.name, oldInput.defaultValue.toString());
+ } else if (oldInput instanceof OldInput.OldCheckBox) {
+ convertedInput = new CheckBox(oldInput.name, (List) oldInput.defaultValue, oldInput.options);
+ } else if (oldInput instanceof OldInput && oldInput.options != null) {
+ convertedInput = new Select(oldInput.name, oldInput.defaultValue, oldInput.options);
+ } else {
+ throw new RuntimeException("Can not convert this OldInput.");
+ }
+ convertedInput.setDisplayName(oldInput.getDisplayName());
+ convertedInput.setHidden(oldInput.isHidden());
+ convertedInput.setArgument(oldInput.getArgument());
+ return convertedInput;
+ }
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/Input.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/Input.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/Input.java
index 4924b2b..12fa782 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/Input.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/Input.java
@@ -18,6 +18,8 @@
package org.apache.zeppelin.display;
import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.display.ui.*;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import java.io.Serializable;
import java.util.*;
@@ -25,105 +27,43 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Input type.
+ * Base class for dynamic forms. Also used as factory class of dynamic forms.
+ *
+ * @param <T>
*/
-public class Input implements Serializable {
- /**
- * Parameters option.
- */
- public static class ParamOption {
- Object value;
- String displayName;
-
- public ParamOption(Object value, String displayName) {
- super();
- this.value = value;
- this.displayName = displayName;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- ParamOption that = (ParamOption) o;
-
- if (value != null ? !value.equals(that.value) : that.value != null) return false;
- return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
-
- }
-
- @Override
- public int hashCode() {
- int result = value != null ? value.hashCode() : 0;
- result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
- return result;
- }
-
- public Object getValue() {
- return value;
- }
-
- public void setValue(Object value) {
- this.value = value;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- }
-
- String name;
- String displayName;
- String type;
- String argument;
- Object defaultValue;
- ParamOption[] options;
- boolean hidden;
-
- public Input(String name, Object defaultValue, String type) {
- this.name = name;
- this.displayName = name;
- this.defaultValue = defaultValue;
- this.type = type;
+public class Input<T> implements Serializable {
+
+ // @TODO(zjffdu). Use gson's RuntimeTypeAdapterFactory and remove the old input form support
+ // in future.
+ public static final RuntimeTypeAdapterFactory TypeAdapterFactory =
+ RuntimeTypeAdapterFactory.of(Input.class, "type")
+ .registerSubtype(TextBox.class, "TextBox")
+ .registerSubtype(Select.class, "Select")
+ .registerSubtype(CheckBox.class, "CheckBox")
+ .registerSubtype(OldInput.OldTextBox.class, "input")
+ .registerSubtype(OldInput.OldSelect.class, "select")
+ .registerSubtype(OldInput.OldCheckBox.class, "checkbox")
+ .registerSubtype(OldInput.class, null);
+
+ protected String name;
+ protected String displayName;
+ protected T defaultValue;
+ protected boolean hidden;
+ protected String argument;
+
+ public Input() {
}
- public Input(String name, Object defaultValue, String type, ParamOption[] options) {
- this.name = name;
- this.displayName = name;
- this.defaultValue = defaultValue;
- this.type = type;
- this.options = options;
- }
-
- public Input(String name, String displayName, String type, String argument, Object defaultValue,
- ParamOption[] options, boolean hidden) {
- super();
- this.name = name;
- this.displayName = displayName;
- this.argument = argument;
- this.type = type;
- this.defaultValue = defaultValue;
- this.options = options;
- this.hidden = hidden;
- }
-
- @Override
- public boolean equals(Object o) {
- return name.equals(((Input) o).getName());
+ public boolean isHidden() {
+ return hidden;
}
public String getName() {
- return name;
+ return this.name;
}
- public void setName(String name) {
- this.name = name;
+ public T getDefaultValue() {
+ return defaultValue;
}
public String getDisplayName() {
@@ -134,41 +74,37 @@ public class Input implements Serializable {
this.displayName = displayName;
}
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
+ public void setArgument(String argument) {
+ this.argument = argument;
}
- public Object getDefaultValue() {
- return defaultValue;
+ public void setHidden(boolean hidden) {
+ this.hidden = hidden;
}
- public void setDefaultValue(Object defaultValue) {
- this.defaultValue = defaultValue;
+ public String getArgument() {
+ return argument;
}
- public ParamOption[] getOptions() {
- return options;
+ public static TextBox textbox(String name, String defaultValue) {
+ return new TextBox(name, defaultValue);
}
- public void setOptions(ParamOption[] options) {
- this.options = options;
+ public static Select select(String name, Object defaultValue, ParamOption[] options) {
+ return new Select(name, defaultValue, options);
}
- public boolean isHidden() {
- return hidden;
+ public static CheckBox checkbox(String name, Object[] defaultChecked, ParamOption[] options) {
+ return new CheckBox(name, defaultChecked, options);
}
// Syntax of variables: ${TYPE:NAME=DEFAULT_VALUE1|DEFAULT_VALUE2|...,VALUE1|VALUE2|...}
// Type is optional. Type may contain an optional argument with syntax: TYPE(ARG)
// NAME and VALUEs may contain an optional display name with syntax: NAME(DISPLAY_NAME)
// DEFAULT_VALUEs may not contain display name
- // Examples: ${age} input form without default value
- // ${age=3} input form with default value
- // ${age(Age)=3} input form with display name and default value
+ // Examples: ${age} textbox form without default value
+ // ${age=3} textbox form with default value
+ // ${age(Age)=3} textbox form with display name and default value
// ${country=US(United States)|UK|JP} select form with
// ${checkbox( or ):country(Country)=US|JP,US(United States)|UK|JP}
// checkbox form with " or " as delimiter: will be
@@ -282,7 +218,22 @@ public class Input implements Serializable {
}
- return new Input(varName, displayName, type, arg, defaultValue, paramOptions, hidden);
+ Input input = null;
+ if (type == null) {
+ if (paramOptions == null) {
+ input = new TextBox(varName, (String) defaultValue);
+ } else {
+ input = new Select(varName, defaultValue, paramOptions);
+ }
+ } else if (type.equals("checkbox")) {
+ input = new CheckBox(varName, (Object[]) defaultValue, paramOptions);
+ } else {
+ throw new RuntimeException("Could not recognize dynamic form with type: " + type);
+ }
+ input.setArgument(arg);
+ input.setDisplayName(displayName);
+ input.setHidden(hidden);
+ return input;
}
public static LinkedHashMap<String, Input> extractSimpleQueryForm(String script) {
@@ -314,11 +265,12 @@ public class Input implements Serializable {
if (params.containsKey(input.name)) {
value = params.get(input.name);
} else {
- value = input.defaultValue;
+ value = input.getDefaultValue();
}
String expanded;
if (value instanceof Object[] || value instanceof Collection) { // multi-selection
+ OptionInput optionInput = (OptionInput) input;
String delimiter = input.argument;
if (delimiter == null) {
delimiter = DEFAULT_DELIMITER;
@@ -327,7 +279,7 @@ public class Input implements Serializable {
: Arrays.asList((Object[]) value);
List<Object> validChecked = new LinkedList<>();
for (Object o : checked) { // filter out obsolete checked values
- for (ParamOption option : input.getOptions()) {
+ for (ParamOption option : optionInput.getOptions()) {
if (option.getValue().equals(o)) {
validChecked.add(o);
break;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/OldInput.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/OldInput.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/OldInput.java
new file mode 100644
index 0000000..7c67dad
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/OldInput.java
@@ -0,0 +1,87 @@
+/*
+ * 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.display;
+
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
+
+/**
+ * Old Input type.
+ * The reason I still keep Old Input is for compatibility. There's one bug in the old input forms.
+ * There's 2 ways to create input forms: frontend & backend.
+ * The bug is in frontend. The type would not be set correctly when input form
+ * is created in frontend (Input.getInputForm).
+ */
+public class OldInput extends Input<Object> {
+
+ ParamOption[] options;
+
+ public OldInput() {}
+
+ public OldInput(String name, Object defaultValue) {
+ this.name = name;
+ this.displayName = name;
+ this.defaultValue = defaultValue;
+ }
+
+ public OldInput(String name, Object defaultValue, ParamOption[] options) {
+ this.name = name;
+ this.displayName = name;
+ this.defaultValue = defaultValue;
+ this.options = options;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return name.equals(((OldInput) o).getName());
+ }
+
+ public ParamOption[] getOptions() {
+ return options;
+ }
+
+ public void setOptions(ParamOption[] options) {
+ this.options = options;
+ }
+
+ /**
+ *
+ */
+ public static class OldTextBox extends OldInput {
+ public OldTextBox(String name, Object defaultValue) {
+ super(name, defaultValue);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class OldSelect extends OldInput {
+ public OldSelect(String name, Object defaultValue, ParamOption[] options) {
+ super(name, defaultValue, options);
+ }
+ }
+
+ /**
+ *
+ */
+ public static class OldCheckBox extends OldInput {
+ public OldCheckBox(String name, Object defaultValue, ParamOption[] options) {
+ super(name, defaultValue, options);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/RuntimeTypeAdapterFactory.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/RuntimeTypeAdapterFactory.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/RuntimeTypeAdapterFactory.java
new file mode 100644
index 0000000..da05caa
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/RuntimeTypeAdapterFactory.java
@@ -0,0 +1,149 @@
+/*
+ * 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.display;
+
+import com.google.gson.*;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Copied from gson with minor changes to support old input forms
+ *
+ * @param <T>
+ */
+public class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
+ private final Class<?> baseType;
+ private final String typeFieldName;
+ private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
+ private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
+
+ private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
+ if (typeFieldName == null || baseType == null) {
+ throw new NullPointerException();
+ }
+ this.baseType = baseType;
+ this.typeFieldName = typeFieldName;
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code
+ * typeFieldName} as the type field name. Type field names are case sensitive.
+ */
+ public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
+ return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
+ }
+
+ /**
+ * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
+ * the type field name.
+ */
+ public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
+ return new RuntimeTypeAdapterFactory<T>(baseType, "type");
+ }
+
+ /**
+ * Registers {@code type} identified by {@code label}. Labels are case
+ * sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or {@code label}
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
+ throw new IllegalArgumentException("types and labels must be unique");
+ }
+ labelToSubtype.put(label, type);
+ subtypeToLabel.put(type, label);
+ return this;
+ }
+
+ /**
+ * Registers {@code type} identified by its {@link Class#getSimpleName simple
+ * name}. Labels are case sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or its simple name
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
+ return registerSubtype(type, type.getSimpleName());
+ }
+
+ public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
+ if (type.getRawType() != baseType) {
+ return null;
+ }
+
+ final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<String, TypeAdapter<?>>();
+ final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate =
+ new LinkedHashMap<Class<?>, TypeAdapter<?>>();
+ for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
+ TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
+ labelToDelegate.put(entry.getKey(), delegate);
+ subtypeToDelegate.put(entry.getValue(), delegate);
+ }
+
+ return new TypeAdapter<R>() {
+ @Override public R read(JsonReader in) throws IOException {
+ JsonElement jsonElement = Streams.parse(in);
+ JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
+ String label = (labelJsonElement == null ? null : labelJsonElement.getAsString());
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
+ if (delegate == null) {
+ throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ + label + "; did you forget to register a subtype?");
+ }
+ return delegate.fromJsonTree(jsonElement);
+ }
+
+ @Override public void write(JsonWriter out, R value) throws IOException {
+ Class<?> srcType = value.getClass();
+ String label = subtypeToLabel.get(srcType);
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
+ if (delegate == null) {
+ throw new JsonParseException("cannot serialize " + srcType.getName()
+ + "; did you forget to register a subtype?");
+ }
+ JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
+ if (jsonObject.has(typeFieldName) && !srcType.getSimpleName().equals("OldInput")) {
+ throw new JsonParseException("cannot serialize " + srcType.getName()
+ + " because it already defines a field named " + typeFieldName);
+ }
+ JsonObject clone = new JsonObject();
+ if (!srcType.getSimpleName().equals("OldInput")) {
+ clone.add(typeFieldName, new JsonPrimitive(label));
+ }
+ for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
+ clone.add(e.getKey(), e.getValue());
+ }
+ Streams.write(clone, out);
+ }
+ }.nullSafe();
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/CheckBox.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/CheckBox.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/CheckBox.java
new file mode 100644
index 0000000..f9b4650
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/CheckBox.java
@@ -0,0 +1,45 @@
+/*
+ * 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.display.ui;
+
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Html Checkbox
+ */
+public class CheckBox extends OptionInput<Object[]> {
+
+ public CheckBox() {
+ }
+
+ public CheckBox(String name, Object[] defaultValue, ParamOption[] options) {
+ this.name = name;
+ this.displayName = name;
+ this.defaultValue = defaultValue;
+ this.options = options;
+ }
+
+ public CheckBox(String name, Collection<Object> defaultValue, ParamOption[] options) {
+ this(name, defaultValue.toArray(), options);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/OptionInput.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/OptionInput.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/OptionInput.java
new file mode 100644
index 0000000..d5a1c0d
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/OptionInput.java
@@ -0,0 +1,85 @@
+/*
+ * 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.display.ui;
+
+import org.apache.zeppelin.display.Input;
+
+/**
+ * Base class for Input with options
+ *
+ * @param <T>
+ */
+public abstract class OptionInput<T> extends Input<T> {
+
+ /**
+ * Parameters option.
+ */
+ public static class ParamOption {
+ Object value;
+ String displayName;
+
+ public ParamOption(Object value, String displayName) {
+ super();
+ this.value = value;
+ this.displayName = displayName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ParamOption that = (ParamOption) o;
+
+ if (value != null ? !value.equals(that.value) : that.value != null) return false;
+ return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = value != null ? value.hashCode() : 0;
+ result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
+ return result;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ }
+
+ protected ParamOption[] options;
+
+ public ParamOption[] getOptions() {
+ return options;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Select.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Select.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Select.java
new file mode 100644
index 0000000..212d3d7
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Select.java
@@ -0,0 +1,36 @@
+/*
+ * 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.display.ui;
+
+/**
+ * Html Dropdown list
+ */
+public class Select extends OptionInput<Object> {
+
+ public Select() {
+
+ }
+
+ public Select(String name, Object defaultValue, ParamOption[] options) {
+ this.name = name;
+ this.displayName = name;
+ this.defaultValue = defaultValue;
+ this.options = options;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/TextBox.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/TextBox.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/TextBox.java
new file mode 100644
index 0000000..b9f9946
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/TextBox.java
@@ -0,0 +1,38 @@
+/*
+ * 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.display.ui;
+
+import org.apache.zeppelin.display.Input;
+
+/**
+ * Html TextBox control
+ */
+public class TextBox extends Input<String> {
+
+ public TextBox() {
+
+ }
+
+ public TextBox(String name, String defaultValue) {
+ this.name = name;
+ this.displayName = name;
+ this.defaultValue = defaultValue;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java
index 2f9d2bb..123ad75 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java
@@ -381,14 +381,14 @@ public class RemoteInterpreter extends Interpreter {
context.getConfig().putAll(remoteConfig);
if (form == FormType.NATIVE) {
- GUI remoteGui = gson.fromJson(remoteResult.getGui(), GUI.class);
+ GUI remoteGui = GUI.fromJson(remoteResult.getGui());
currentGUI.clear();
currentGUI.setParams(remoteGui.getParams());
currentGUI.setForms(remoteGui.getForms());
} else if (form == FormType.SIMPLE) {
final Map<String, Input> currentForms = currentGUI.getForms();
final Map<String, Object> currentParams = currentGUI.getParams();
- final GUI remoteGUI = gson.fromJson(remoteResult.getGui(), GUI.class);
+ final GUI remoteGUI = GUI.fromJson(remoteResult.getGui());
final Map<String, Input> remoteForms = remoteGUI.getForms();
final Map<String, Object> remoteParams = remoteGUI.getParams();
currentForms.putAll(remoteForms);
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
index 3b7ec5c..6c43813 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
@@ -592,7 +592,7 @@ public class RemoteInterpreterServer
gson.fromJson(ric.getAuthenticationInfo(), AuthenticationInfo.class),
(Map<String, Object>) gson.fromJson(ric.getConfig(),
new TypeToken<Map<String, Object>>() {}.getType()),
- gson.fromJson(ric.getGui(), GUI.class),
+ GUI.fromJson(ric.getGui()),
interpreterGroup.getAngularObjectRegistry(),
interpreterGroup.getResourcePool(),
contextRunners, output, remoteWorksController, eventClient);
@@ -737,7 +737,7 @@ public class RemoteInterpreterServer
result.code().name(),
msg,
gson.toJson(config),
- gson.toJson(gui));
+ gui.toJson());
}
@Override
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/GUITest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/GUITest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/GUITest.java
new file mode 100644
index 0000000..6def2e7
--- /dev/null
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/GUITest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.display;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.zeppelin.display.ui.CheckBox;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
+import org.apache.zeppelin.display.ui.Select;
+import org.apache.zeppelin.display.ui.TextBox;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class GUITest {
+
+ private ParamOption[] options = new ParamOption[]{
+ new ParamOption("1", "value_1"),
+ new ParamOption("2", "value_2")
+ };
+
+ private List<Object> checkedItems;
+
+ @Before
+ public void setUp() {
+ checkedItems = new ArrayList<>();
+ checkedItems.add("1");
+ }
+
+ @Test
+ public void testGson() {
+ GUI gui = new GUI();
+ gui.textbox("textbox_1", "default_text_1");
+ gui.select("select_1", "1", options);
+ List<Object> list = new ArrayList();
+ list.add("1");
+ gui.checkbox("checkbox_1", list, options);
+
+ String json = gui.toJson();
+ System.out.println(json);
+ GUI gui2 = GUI.fromJson(json);
+ assertEquals(gui2.toJson(), json);
+ assertEquals(gui2.forms, gui2.forms);
+ assertEquals(gui2.params, gui2.params);
+ }
+
+ // Case 1. Old input forms are created in backend, in this case type is always set
+ @Test
+ public void testOldGson_1() throws IOException {
+
+ GUI gui = new GUI();
+ gui.forms.put("textbox_1", new OldInput.OldTextBox("textbox_1", "default_text_1"));
+ gui.forms.put("select_1", new OldInput.OldSelect("select_1", "1", options));
+ gui.forms.put("checkbox_1",
+ new OldInput.OldCheckBox("checkbox_1", checkedItems, options));
+
+ // convert to old json format.
+ String json = gui.toJson();
+
+ // convert to new input forms
+ GUI gui2 = GUI.fromJson(json);
+ assertTrue(3 == gui2.forms.size());
+ assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
+ assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
+ assertTrue(gui2.forms.get("select_1") instanceof Select);
+ assertEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
+ assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
+ assertEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
+ }
+
+ // Case 2. Old input forms are created in frontend, in this case type is only set for checkbox
+ // Actually this is a bug due to method Input#getInputForm
+ @Test
+ public void testOldGson_2() throws IOException {
+
+ GUI gui = new GUI();
+ gui.forms.put("textbox_1", new OldInput("textbox_1", "default_text_1"));
+ gui.forms.put("select_1", new OldInput("select_1", "1", options));
+ gui.forms.put("checkbox_1",
+ new OldInput.OldCheckBox("checkbox_1", checkedItems, options));
+
+ // convert to old json format.
+ String json = gui.toJson();
+
+ // convert to new input forms
+ GUI gui2 = GUI.fromJson(json);
+ assertTrue(3 == gui2.forms.size());
+ assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
+ assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
+ assertTrue(gui2.forms.get("select_1") instanceof Select);
+ assertEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
+ assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
+ assertEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/InputTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/InputTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/InputTest.java
index b6f1e3e..d15fab4 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/InputTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/display/InputTest.java
@@ -20,16 +20,19 @@ package org.apache.zeppelin.display;
import java.util.HashMap;
import java.util.Map;
+import org.apache.zeppelin.display.ui.CheckBox;
+import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
+import org.apache.zeppelin.display.ui.Select;
+import org.apache.zeppelin.display.ui.TextBox;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
-import org.apache.zeppelin.display.Input.ParamOption;
-
public class InputTest {
@Before
@@ -42,7 +45,7 @@ public class InputTest {
@Test
public void testFormExtraction() {
- // input form
+ // textbox form
String script = "${input_form=}";
Map<String, Input> forms = Input.extractSimpleQueryForm(script);
assertEquals(1, forms.size());
@@ -50,50 +53,57 @@ public class InputTest {
assertEquals("input_form", form.name);
assertNull(form.displayName);
assertEquals("", form.defaultValue);
- assertNull(form.options);
+ assertTrue(form instanceof TextBox);
- // input form with display name & default value
+ // textbox form with display name & default value
script = "${input_form(Input Form)=xxx}";
forms = Input.extractSimpleQueryForm(script);
form = forms.get("input_form");
assertEquals("xxx", form.defaultValue);
+ assertTrue(form instanceof TextBox);
// selection form
script = "${select_form(Selection Form)=op1,op1|op2(Option 2)|op3}";
form = Input.extractSimpleQueryForm(script).get("select_form");
assertEquals("select_form", form.name);
assertEquals("op1", form.defaultValue);
+ assertTrue(form instanceof Select);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
- new ParamOption("op2", "Option 2"), new ParamOption("op3", null)}, form.options);
+ new ParamOption("op2", "Option 2"), new ParamOption("op3", null)},
+ ((Select) form).getOptions());
// checkbox form
script = "${checkbox:checkbox_form=op1,op1|op2|op3}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
- assertEquals("checkbox", form.type);
+ assertTrue(form instanceof CheckBox);
+
assertArrayEquals(new Object[]{"op1"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
- new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
+ new ParamOption("op2", null), new ParamOption("op3", null)},
+ ((CheckBox) form).getOptions());
// checkbox form with multiple default checks
script = "${checkbox:checkbox_form(Checkbox Form)=op1|op3,op1(Option 1)|op2|op3}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
- assertEquals("checkbox", form.type);
+ assertTrue(form instanceof CheckBox);
assertArrayEquals(new Object[]{"op1", "op3"}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
- new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
+ new ParamOption("op2", null), new ParamOption("op3", null)},
+ ((CheckBox) form).getOptions());
// checkbox form with no default check
script = "${checkbox:checkbox_form(Checkbox Form)=,op1(Option 1)|op2(Option 2)|op3(Option 3)}";
form = Input.extractSimpleQueryForm(script).get("checkbox_form");
assertEquals("checkbox_form", form.name);
assertEquals("Checkbox Form", form.displayName);
- assertEquals("checkbox", form.type);
+ assertTrue(form instanceof CheckBox);
assertArrayEquals(new Object[]{}, (Object[]) form.defaultValue);
assertArrayEquals(new ParamOption[]{new ParamOption("op1", "Option 1"),
- new ParamOption("op2", "Option 2"), new ParamOption("op3", "Option 3")}, form.options);
+ new ParamOption("op2", "Option 2"), new ParamOption("op3", "Option 3")},
+ ((CheckBox) form).getOptions());
}
@@ -125,4 +135,5 @@ public class InputTest {
assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1\n" +
"NEW_CHECKED=nc_a and nc_c", replaced);
}
+
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/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 1aa4f28..10ed00e 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
@@ -45,6 +45,7 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectRegistryListener;
+import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.helium.ApplicationEventListener;
import org.apache.zeppelin.helium.HeliumPackage;
import org.apache.zeppelin.interpreter.Interpreter;
@@ -134,7 +135,9 @@ public class NotebookServer extends WebSocketServlet
}
}
}
- }).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create();
+ }).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
+ .registerTypeAdapterFactory(Input.TypeAdapterFactory).create();
+
final Map<String, List<NotebookSocket>> noteSocketMap = new HashMap<>();
final Queue<NotebookSocket> connectedSockets = new ConcurrentLinkedQueue<>();
final Map<String, Queue<NotebookSocket>> userConnectedSockets = new ConcurrentHashMap<>();
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
index 8e09f00..add23ac 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
@@ -548,7 +548,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
try {
createNewNote();
- setTextOfParagraph(1, "%spark println(\"Hello \"+z.input(\"name\", \"world\")) ");
+ setTextOfParagraph(1, "%spark println(\"Hello \"+z.textbox(\"name\", \"world\")) ");
runParagraph(1);
waitForParagraph(1, "FINISHED");
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
index eb8186e..77e0844 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
@@ -490,7 +490,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
- String code = "%spark.spark println(z.input(\"my_input\", \"default_name\"))\n" +
+ String code = "%spark.spark println(z.textbox(\"my_input\", \"default_name\"))\n" +
"println(z.select(\"my_select\", \"1\"," +
"Seq((\"1\", \"select_1\"), (\"2\", \"select_2\"))))\n" +
"val items=z.checkbox(\"my_checkbox\", Seq(\"2\"), " +
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
index 64da3cf..249e7c1 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
@@ -20,7 +20,7 @@ limitations under the License.
<label class="control-label input-sm" ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }">{{formulaire.name}}</label>
<div>
<input class="form-control input-sm"
- ng-if="!paragraph.settings.forms[formulaire.name].options"
+ ng-if="paragraph.settings.forms[formulaire.name].type == 'TextBox'"
ng-enter="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
@@ -28,7 +28,7 @@ limitations under the License.
</div>
<div ng-if="paragraph.config.runOnSelectionChange == true">
<select class="form-control input-sm"
- ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
+ ng-if="paragraph.settings.forms[formulaire.name].type == 'Select'"
ng-change="runParagraphFromButton(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
@@ -38,16 +38,16 @@ limitations under the License.
</div>
<div ng-if="paragraph.config.runOnSelectionChange == false">
<select class="form-control input-sm"
- ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
- ng-enter="runParagraphFromButton(getEditorValue())"
- ng-model="paragraph.settings.params[formulaire.name]"
- ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
- name="{{formulaire.name}}"
- ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options">
+ ng-if="paragraph.settings.forms[formulaire.name].type == 'Select'"
+ ng-enter="runParagraphFromButton(getEditorValue())"
+ ng-model="paragraph.settings.params[formulaire.name]"
+ ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
+ name="{{formulaire.name}}"
+ ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options">
</select>
</div>
<div ng-if="paragraph.config.runOnSelectionChange == true &&
- paragraph.settings.forms[formulaire.name].type == 'checkbox'">
+ paragraph.settings.forms[formulaire.name].type == 'CheckBox'">
<label ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
class="checkbox-item input-sm">
<input type="checkbox"
@@ -57,7 +57,7 @@ limitations under the License.
</label>
</div>
<div ng-if="paragraph.config.runOnSelectionChange == false &&
- paragraph.settings.forms[formulaire.name].type == 'checkbox'">
+ paragraph.settings.forms[formulaire.name].type == 'CheckBox'">
<label ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
class="checkbox-item input-sm">
<input type="checkbox"
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
index 75e1b2e..186de88 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
@@ -54,6 +54,7 @@ function websocketEvents($rootScope, $websocket, $location, baseUrlSrv) {
if (event.data) {
payload = angular.fromJson(event.data);
}
+ console.log('Receive Json << %o', event.data)
console.log('Receive << %o, %o', payload.op, payload);
var op = payload.op;
var data = payload.data;
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/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 3e6ab23..dfe39e9 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
@@ -26,6 +26,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import com.google.gson.GsonBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.display.AngularObject;
@@ -54,6 +55,9 @@ import com.google.gson.Gson;
public class Note implements Serializable, ParagraphJobListener {
private static final Logger logger = LoggerFactory.getLogger(Note.class);
private static final long serialVersionUID = 7920699076577612429L;
+ private static final Gson gson = new GsonBuilder()
+ .registerTypeAdapterFactory(Input.TypeAdapterFactory)
+ .create();
// threadpool for delayed persist of note
private static final ScheduledThreadPoolExecutor delayedPersistThreadPool =
@@ -882,4 +886,19 @@ public class Note implements Serializable, ParagraphJobListener {
this.noteEventListener = noteEventListener;
}
+ public String toJson() {
+ return gson.toJson(this);
+ }
+
+ public static Note fromJson(String json) {
+ Note note = gson.fromJson(json, Note.class);
+ convertOldInput(note);
+ return note;
+ }
+
+ private static void convertOldInput(Note note) {
+ for (Paragraph p : note.paragraphs) {
+ p.settings.convertOldInput();
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
index 79f5dd6..5ef3c16 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
@@ -138,7 +138,7 @@ public class AzureNotebookRepo implements NotebookRepo {
Gson gson = gsonBuilder.registerTypeAdapter(Date.class, new NotebookImportDeserializer())
.create();
- Note note = gson.fromJson(json, Note.class);
+ Note note = Note.fromJson(json);
for (Paragraph p : note.getParagraphs()) {
if (p.getStatus() == Job.Status.PENDING || p.getStatus() == Job.Status.RUNNING) {
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
index bd7fe1a..71fa19f 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
@@ -202,7 +202,7 @@ public class S3NotebookRepo implements NotebookRepo {
Note note;
try (InputStream ins = s3object.getObjectContent()) {
String json = IOUtils.toString(ins, conf.getString(ConfVars.ZEPPELIN_ENCODING));
- note = gson.fromJson(json, Note.class);
+ note = Note.fromJson(json);
}
for (Paragraph p : note.getParagraphs()) {
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7b585c73/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
index 04a7075..0251569 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
@@ -175,7 +175,7 @@ public class VFSNotebookRepo implements NotebookRepo {
String json = IOUtils.toString(ins, conf.getString(ConfVars.ZEPPELIN_ENCODING));
ins.close();
- Note note = gson.fromJson(json, Note.class);
+ Note note = Note.fromJson(json);
// note.setReplLoader(replLoader);
// note.jobListenerFactory = jobListenerFactory;