You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2016/02/28 16:55:36 UTC
incubator-zeppelin git commit: Add checkbox as a type of dynamic forms
Repository: incubator-zeppelin
Updated Branches:
refs/heads/master b960f09d0 -> 4df083dca
Add checkbox as a type of dynamic forms
### What is this PR for?
1. Add checkbox support in dynamic forms
2. Fix ZEPPELIN-699: Cannot select the first item of dynamic forms when it is just created
### What type of PR is it?
Feature & Bug Fix
### Todos
* [x] Compatibility test with notes from previous versions
* [x] Documentation
* [x] Nice CSS layout
* [x] Support checkbox creation by string substitution
* [x] Support pyspark
### Is there a relevant Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-671
https://issues.apache.org/jira/browse/ZEPPELIN-669
### How should this be tested?
1. Create a %spark paragraph: `val a = z.checkbox("my_check", Seq(("a", "a"), ("b", "b"), ("c", "c")))`
2. Toggle the checkboxes and see the outcome
### Screenshots (if appropriate)
![igojnqh0mz](https://cloud.githubusercontent.com/assets/3282033/13136828/929ec1ec-d5d1-11e5-89d9-98659f444f37.gif)
### Questions:
* Does the licenses files need update?
NO
* Is there breaking changes for older versions?
Sort of. I am not sure whether Input.type (form.type) has already been used in other places
* Does this needs documentation?
YES
Author: Zhong Wang <wa...@gmail.com>
Author: Zhong Wang <zh...@Zhongs-MacBook-Pro.local>
Closes #713 from zhongneu/dynamic-forms-checkbox and squashes the following commits:
0d3e566 [Zhong Wang] change css class name from checkbox-group to checkbox-item
584bab8 [Zhong Wang] some cleanups & fix an issue of obsolete values
790d0f0 [Zhong Wang] add pyspark support
2af3e64 [Zhong Wang] fix docs
c0683f1 [Zhong Wang] add documentation for checkbox forms
9336b61 [Zhong Wang] refactoring the form parsing / substitution code to support delimiter for multi-selection forms
8035cb1 [Zhong Wang] fix a display issue in query mode if no display name is specified
2a634be [Zhong Wang] fix an issue with invalid options: related to ZEPPELIN-674
db62ca7 [Zhong Wang] revoke changes for hide/show; improve compatibility of older versions of notebooks
b799fb0 [Zhong Wang] add option to configure hidden behavior for checkboxes
f10d6e2 [Zhong Wang] better CSS layout & add show/hide option
3273230 [Zhong Wang] fix a bug: the checkbox should show display name
e190707 [Zhong Wang] fix several bugs, including ZEPPELIN-669
6969e8c [Zhong Wang] first attempt of adding checkbox to dynamic forms
Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/4df083dc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/4df083dc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/4df083dc
Branch: refs/heads/master
Commit: 4df083dca9e0965b42d9dce9912e902d9afe3163
Parents: b960f09
Author: Zhong Wang <wa...@gmail.com>
Authored: Thu Feb 25 21:30:57 2016 -0800
Committer: Lee moon soo <mo...@apache.org>
Committed: Sun Feb 28 07:59:05 2016 -0800
----------------------------------------------------------------------
.../zeppelin/img/screenshots/form_checkbox.png | Bin 0 -> 23030 bytes
.../img/screenshots/form_checkbox_delimiter.png | Bin 0 -> 18913 bytes
.../img/screenshots/form_checkbox_prog.png | Bin 0 -> 37691 bytes
docs/manual/dynamicform.md | 33 +++
.../apache/zeppelin/spark/ZeppelinContext.java | 25 +-
.../main/resources/python/zeppelin_pyspark.py | 10 +
.../java/org/apache/zeppelin/display/GUI.java | 32 ++-
.../java/org/apache/zeppelin/display/Input.java | 259 ++++++++++++-------
.../org/apache/zeppelin/display/InputTest.java | 93 ++++++-
.../paragraph-parameterizedQueryForm.html | 13 +-
.../notebook/paragraph/paragraph.controller.js | 13 +-
.../src/app/notebook/paragraph/paragraph.css | 9 +
12 files changed, 377 insertions(+), 110 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/docs/assets/themes/zeppelin/img/screenshots/form_checkbox.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/screenshots/form_checkbox.png b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox.png
new file mode 100644
index 0000000..ffc83cc
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png
new file mode 100644
index 0000000..ed58f0e
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png
new file mode 100644
index 0000000..57b52da
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/docs/manual/dynamicform.md
----------------------------------------------------------------------
diff --git a/docs/manual/dynamicform.md b/docs/manual/dynamicform.md
index 5b754f0..0622287 100644
--- a/docs/manual/dynamicform.md
+++ b/docs/manual/dynamicform.md
@@ -54,6 +54,16 @@ Also you can separate option's display name and value, using _${formName=default
<img src="/assets/themes/zeppelin/img/screenshots/form_select_displayname.png" />
+#### Checkbox form
+
+For multi-selection, you can create a checkbox form using _${checkbox:formName=defaultValue1|defaultValue2...,option1|option2...}_. The variable will be substituted by a comma-separated string based on the selected items. For example:
+
+<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox.png">
+
+Besides, you can specify the delimiter using _${checkbox(delimiter):formName=...}_:
+
+<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox_delimiter.png">
+
### Creates Programmatically
Some language backend uses programmatic way to create form. For example [ZeppelinContext](../interpreter/spark.html#zeppelincontext) provides form creation API
@@ -134,3 +144,26 @@ print("Hello "+z.select("day", [("1","mon"),
</div>
</div>
<img src="/assets/themes/zeppelin/img/screenshots/form_select_prog.png" />
+
+#### Checkbox form
+<div class="codetabs">
+ <div data-lang="scala" markdown="1">
+
+{% highlight scala %}
+%spark
+val options = Seq(("apple","Apple"), ("banana","Banana"), ("orange","Orange"))
+println("Hello "+z.checkbox("fruit", options).mkString(" and "))
+{% endhighlight %}
+
+ </div>
+ <div data-lang="python" markdown="1">
+
+{% highlight python %}
+%pyspark
+options = [("apple","Apple"), ("banana","Banana"), ("orange","Orange")]
+print("Hello "+ " and ".join(z.checkbox("fruit", options, ["apple"])))
+{% endhighlight %}
+
+ </div>
+</div>
+<img src="/assets/themes/zeppelin/img/screenshots/form_checkbox_prog.png" />
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/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 a25c2c2..88094b5 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java
@@ -17,7 +17,9 @@
package org.apache.zeppelin.spark;
+import static scala.collection.JavaConversions.asJavaCollection;
import static scala.collection.JavaConversions.asJavaIterable;
+import static scala.collection.JavaConversions.asScalaIterable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -84,6 +86,27 @@ public class ZeppelinContext {
public Object select(String name, Object defaultValue,
scala.collection.Iterable<Tuple2<Object, String>> options) {
+ return gui.select(name, defaultValue, tuplesToParamOptions(options));
+ }
+
+ public scala.collection.Iterable<Object> checkbox(String name,
+ scala.collection.Iterable<Tuple2<Object, String>> options) {
+ List<Object> allChecked = new LinkedList<Object>();
+ for (Tuple2<Object, String> option : asJavaIterable(options)) {
+ allChecked.add(option._1());
+ }
+ return checkbox(name, asScalaIterable(allChecked), options);
+ }
+
+ public scala.collection.Iterable<Object> checkbox(String name,
+ scala.collection.Iterable<Object> defaultChecked,
+ scala.collection.Iterable<Tuple2<Object, String>> options) {
+ return asScalaIterable(gui.checkbox(name, asJavaCollection(defaultChecked),
+ tuplesToParamOptions(options)));
+ }
+
+ private ParamOption[] tuplesToParamOptions(
+ scala.collection.Iterable<Tuple2<Object, String>> options) {
int n = options.size();
ParamOption[] paramOptions = new ParamOption[n];
Iterator<Tuple2<Object, String>> it = asJavaIterable(options).iterator();
@@ -94,7 +117,7 @@ public class ZeppelinContext {
paramOptions[i++] = new ParamOption(valueAndDisplayValue._1(), valueAndDisplayValue._2());
}
- return gui.select(name, defaultValue, paramOptions);
+ return paramOptions;
}
public void setGui(GUI o) {
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/spark/src/main/resources/python/zeppelin_pyspark.py
----------------------------------------------------------------------
diff --git a/spark/src/main/resources/python/zeppelin_pyspark.py b/spark/src/main/resources/python/zeppelin_pyspark.py
index 7da0f4e..9b94274 100644
--- a/spark/src/main/resources/python/zeppelin_pyspark.py
+++ b/spark/src/main/resources/python/zeppelin_pyspark.py
@@ -84,6 +84,16 @@ class PyZeppelinContext(dict):
iterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(tuples)
return self.z.select(name, defaultValue, iterables)
+ def checkbox(self, name, options, defaultChecked = None):
+ if defaultChecked is None:
+ defaultChecked = map(lambda items: items[0], options)
+ optionTuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
+ optionIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(optionTuples)
+ defaultCheckedIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(defaultChecked)
+
+ checkedIterables = self.z.checkbox(name, defaultCheckedIterables, optionIterables)
+ return gateway.jvm.scala.collection.JavaConversions.asJavaCollection(checkedIterables)
+
def __tupleToScalaTuple2(self, tuple):
if (len(tuple) == 2):
return gateway.jvm.scala.Tuple2(tuple[0], tuple[1])
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/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 3772610..42a5584 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
@@ -18,7 +18,10 @@
package org.apache.zeppelin.display;
import java.io.Serializable;
+
+import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
@@ -59,7 +62,7 @@ public class GUI implements Serializable {
value = defaultValue;
}
- forms.put(id, new Input(id, defaultValue));
+ forms.put(id, new Input(id, defaultValue, "input"));
return value;
}
@@ -72,10 +75,35 @@ public class GUI implements Serializable {
if (value == null) {
value = defaultValue;
}
- forms.put(id, new Input(id, defaultValue, options));
+ forms.put(id, new Input(id, defaultValue, "select", options));
return value;
}
+ public Collection<Object> checkbox(String id, Collection<Object> defaultChecked,
+ ParamOption[] options) {
+ Collection<Object> checked = (Collection<Object>) params.get(id);
+ if (checked == null) {
+ checked = defaultChecked;
+ }
+ forms.put(id, new Input(id, defaultChecked, "checkbox", options));
+ Collection<Object> filtered = new LinkedList<Object>();
+ for (Object o : checked) {
+ if (isValidOption(o, options)) {
+ filtered.add(o);
+ }
+ }
+ return filtered;
+ }
+
+ private boolean isValidOption(Object o, ParamOption[] options) {
+ for (ParamOption option : options) {
+ if (o.equals(option.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void clear() {
this.forms = new TreeMap<String, Input>();
}
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/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 3b1b1d2..bb0aa4d 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
@@ -17,8 +17,12 @@
package org.apache.zeppelin.display;
+import org.apache.commons.lang.StringUtils;
+
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -43,6 +47,25 @@ public class Input implements Serializable {
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;
}
@@ -64,29 +87,32 @@ public class Input implements Serializable {
String name;
String displayName;
String type;
+ String argument;
Object defaultValue;
ParamOption[] options;
boolean hidden;
- public Input(String name, Object defaultValue) {
+ public Input(String name, Object defaultValue, String type) {
this.name = name;
this.displayName = name;
this.defaultValue = defaultValue;
+ this.type = type;
}
- public Input(String name, Object defaultValue, ParamOption[] options) {
+ 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, Object defaultValue,
+ 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;
@@ -142,6 +168,18 @@ public class Input implements Serializable {
return hidden;
}
+ // 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
+ // ${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
+ // expanded to "US or JP"
+ private static final Pattern VAR_PTN = Pattern.compile("([_])?[$][{]([^=}]*([=][^}]*)?)[}]");
private static String[] getNameAndDisplayName(String str) {
Pattern p = Pattern.compile("([^(]*)\\s*[(]([^)]*)[)]");
@@ -156,140 +194,161 @@ public class Input implements Serializable {
}
private static String[] getType(String str) {
- Pattern p = Pattern.compile("([^:]*)\\s*:\\s*(.*)");
+ Pattern p = Pattern.compile("([^:()]*)\\s*([(][^()]*[)])?\\s*:(.*)");
Matcher m = p.matcher(str.trim());
if (m == null || m.find() == false) {
return null;
}
- String[] ret = new String[2];
+ String[] ret = new String[3];
ret[0] = m.group(1).trim();
- ret[1] = m.group(2).trim();
+ if (m.group(2) != null) {
+ ret[1] = m.group(2).trim().replaceAll("[()]", "");
+ }
+ ret[2] = m.group(3).trim();
return ret;
}
- public static Map<String, Input> extractSimpleQueryParam(String script) {
- Map<String, Input> params = new HashMap<String, Input>();
- if (script == null) {
- return params;
+ private static Input getInputForm(Matcher match) {
+ String hiddenPart = match.group(1);
+ boolean hidden = false;
+ if ("_".equals(hiddenPart)) {
+ hidden = true;
}
- String replaced = script;
+ String m = match.group(2);
- Pattern pattern = Pattern.compile("([_])?[$][{]([^=}]*([=][^}]*)?)[}]");
+ String namePart;
+ String valuePart;
- Matcher match = pattern.matcher(replaced);
- while (match.find()) {
- String hiddenPart = match.group(1);
- boolean hidden = false;
- if ("_".equals(hiddenPart)) {
- hidden = true;
- }
- String m = match.group(2);
+ int p = m.indexOf('=');
+ if (p > 0) {
+ namePart = m.substring(0, p);
+ valuePart = m.substring(p + 1);
+ } else {
+ namePart = m;
+ valuePart = null;
+ }
- String namePart;
- String valuePart;
-
- int p = m.indexOf('=');
- if (p > 0) {
- namePart = m.substring(0, p);
- valuePart = m.substring(p + 1);
- } else {
- namePart = m;
- valuePart = null;
- }
+ String varName;
+ String displayName = null;
+ String type = null;
+ String arg = null;
+ Object defaultValue = "";
+ ParamOption[] paramOptions = null;
+
+ // get var name type
+ String varNamePart;
+ String[] typeArray = getType(namePart);
+ if (typeArray != null) {
+ type = typeArray[0];
+ arg = typeArray[1];
+ varNamePart = typeArray[2];
+ } else {
+ varNamePart = namePart;
+ }
- String varName;
- String displayName = null;
- String type = null;
- String defaultValue = "";
- ParamOption[] paramOptions = null;
+ // get var name and displayname
+ String[] varNameArray = getNameAndDisplayName(varNamePart);
+ if (varNameArray != null) {
+ varName = varNameArray[0];
+ displayName = varNameArray[1];
+ } else {
+ varName = varNamePart.trim();
+ }
- // get var name type
- String varNamePart;
- String[] typeArray = getType(namePart);
- if (typeArray != null) {
- type = typeArray[0];
- varNamePart = typeArray[1];
- } else {
- varNamePart = namePart;
- }
+ // get defaultValue
+ if (valuePart != null) {
+ // find default value
+ int optionP = valuePart.indexOf(",");
+ if (optionP >= 0) { // option available
+ defaultValue = valuePart.substring(0, optionP);
+ if (type != null && type.equals("checkbox")) {
+ // checkbox may contain multiple default checks
+ defaultValue = Input.splitPipe((String) defaultValue);
+ }
+ String optionPart = valuePart.substring(optionP + 1);
+ String[] options = Input.splitPipe(optionPart);
- // get var name and displayname
- String[] varNameArray = getNameAndDisplayName(varNamePart);
- if (varNameArray != null) {
- varName = varNameArray[0];
- displayName = varNameArray[1];
- } else {
- varName = varNamePart.trim();
- }
+ paramOptions = new ParamOption[options.length];
- // get defaultValue
- if (valuePart != null) {
- // find default value
- int optionP = valuePart.indexOf(",");
- if (optionP > 0) { // option available
- defaultValue = valuePart.substring(0, optionP);
- String optionPart = valuePart.substring(optionP + 1);
- String[] options = Input.splitPipe(optionPart);
+ for (int i = 0; i < options.length; i++) {
- paramOptions = new ParamOption[options.length];
+ String[] optNameArray = getNameAndDisplayName(options[i]);
+ if (optNameArray != null) {
+ paramOptions[i] = new ParamOption(optNameArray[0], optNameArray[1]);
+ } else {
+ paramOptions[i] = new ParamOption(options[i], null);
+ }
+ }
- for (int i = 0; i < options.length; i++) {
- String[] optNameArray = getNameAndDisplayName(options[i]);
- if (optNameArray != null) {
- paramOptions[i] = new ParamOption(optNameArray[0], optNameArray[1]);
- } else {
- paramOptions[i] = new ParamOption(options[i], null);
- }
- }
+ } else { // no option
+ defaultValue = valuePart;
+ }
+ }
- } else { // no option
- defaultValue = valuePart;
- }
+ return new Input(varName, displayName, type, arg, defaultValue, paramOptions, hidden);
+ }
- }
+ public static Map<String, Input> extractSimpleQueryParam(String script) {
+ Map<String, Input> params = new HashMap<String, Input>();
+ if (script == null) {
+ return params;
+ }
+ String replaced = script;
- Input param = new Input(varName, displayName, type, defaultValue, paramOptions, hidden);
- params.put(varName, param);
+ Matcher match = VAR_PTN.matcher(replaced);
+ while (match.find()) {
+ Input param = getInputForm(match);
+ params.put(param.name, param);
}
params.remove("pql");
return params;
}
+ private static final String DEFAULT_DELIMITER = ",";
+
public static String getSimpleQuery(Map<String, Object> params, String script) {
String replaced = script;
- for (String key : params.keySet()) {
- Object value = params.get(key);
- replaced =
- replaced.replaceAll("[_]?[$][{]([^:]*[:])?" + key + "([(][^)]*[)])?(=[^}]*)?[}]",
- value.toString());
- }
+ Matcher match = VAR_PTN.matcher(replaced);
+ while (match.find()) {
+ Input input = getInputForm(match);
+ Object value;
+ if (params.containsKey(input.name)) {
+ value = params.get(input.name);
+ } else {
+ value = input.defaultValue;
+ }
- Pattern pattern = Pattern.compile("[$][{]([^=}]*[=][^}]*)[}]");
- while (true) {
- Matcher match = pattern.matcher(replaced);
- if (match != null && match.find()) {
- String m = match.group(1);
- int p = m.indexOf('=');
- String replacement = m.substring(p + 1);
- int optionP = replacement.indexOf(",");
- if (optionP > 0) {
- replacement = replacement.substring(0, optionP);
+ String expanded;
+ if (value instanceof Object[] || value instanceof Collection) { // multi-selection
+ String delimiter = input.argument;
+ if (delimiter == null) {
+ delimiter = DEFAULT_DELIMITER;
}
- replaced =
- replaced.replaceFirst("[_]?[$][{]"
- + m.replaceAll("[(]", ".").replaceAll("[)]", ".").replaceAll("[|]", ".") + "[}]",
- replacement);
- } else {
- break;
+ Collection<Object> checked = value instanceof Collection ? (Collection<Object>) value
+ : Arrays.asList((Object[]) value);
+ List<Object> validChecked = new LinkedList<Object>();
+ for (Object o : checked) { // filter out obsolete checked values
+ for (ParamOption option : input.getOptions()) {
+ if (option.getValue().equals(o)) {
+ validChecked.add(o);
+ break;
+ }
+ }
+ }
+ params.put(input.name, validChecked);
+ expanded = StringUtils.join(validChecked, delimiter);
+ } else { // single-selection
+ expanded = value.toString();
}
+ replaced = match.replaceFirst(expanded);
+ match = VAR_PTN.matcher(replaced);
}
- replaced = replaced.replace("[_]?[$][{]([^=}]*)[}]", "");
return replaced;
}
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/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 626ae99..aeb0d83 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
@@ -17,12 +17,19 @@
package org.apache.zeppelin.display;
-import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
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.assertNull;
+
+import org.apache.zeppelin.display.Input.ParamOption;
+
public class InputTest {
@Before
@@ -34,6 +41,88 @@ public class InputTest {
}
@Test
- public void testDefaultParamReplace() throws IOException{
+ public void testFormExtraction() {
+ // input form
+ String script = "${input_form=}";
+ Map<String, Input> forms = Input.extractSimpleQueryParam(script);
+ assertEquals(1, forms.size());
+ Input form = forms.get("input_form");
+ assertEquals("input_form", form.name);
+ assertNull(form.displayName);
+ assertEquals("", form.defaultValue);
+ assertNull(form.options);
+
+ // input form with display name & default value
+ script = "${input_form(Input Form)=xxx}";
+ forms = Input.extractSimpleQueryParam(script);
+ form = forms.get("input_form");
+ assertEquals("xxx", form.defaultValue);
+
+ // selection form
+ script = "${select_form(Selection Form)=op1,op1|op2(Option 2)|op3}";
+ form = Input.extractSimpleQueryParam(script).get("select_form");
+ assertEquals("select_form", form.name);
+ assertEquals("op1", form.defaultValue);
+ assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
+ new ParamOption("op2", "Option 2"), new ParamOption("op3", null)}, form.options);
+
+ // checkbox form
+ script = "${checkbox:checkbox_form=op1,op1|op2|op3}";
+ form = Input.extractSimpleQueryParam(script).get("checkbox_form");
+ assertEquals("checkbox_form", form.name);
+ assertEquals("checkbox", form.type);
+ assertArrayEquals(new Object[]{"op1"}, (Object[]) form.defaultValue);
+ assertArrayEquals(new ParamOption[]{new ParamOption("op1", null),
+ new ParamOption("op2", null), new ParamOption("op3", null)}, form.options);
+
+ // checkbox form with multiple default checks
+ script = "${checkbox:checkbox_form(Checkbox Form)=op1|op3,op1(Option 1)|op2|op3}";
+ form = Input.extractSimpleQueryParam(script).get("checkbox_form");
+ assertEquals("checkbox_form", form.name);
+ assertEquals("Checkbox Form", form.displayName);
+ assertEquals("checkbox", form.type);
+ 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);
+
+ // checkbox form with no default check
+ script = "${checkbox:checkbox_form(Checkbox Form)=,op1(Option 1)|op2(Option 2)|op3(Option 3)}";
+ form = Input.extractSimpleQueryParam(script).get("checkbox_form");
+ assertEquals("checkbox_form", form.name);
+ assertEquals("Checkbox Form", form.displayName);
+ assertEquals("checkbox", form.type);
+ 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);
+ }
+
+
+ @Test
+ public void testFormSubstitution() {
+ // test form substitution without new forms
+ String script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
+ "CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3}";
+ Map<String, Object> params = new HashMap<String, Object>();
+ params.put("input_form", "some_input");
+ params.put("select_form", "s_op2");
+ params.put("checkbox_form", new String[]{"c_op1", "c_op3"});
+ String replaced = Input.getSimpleQuery(params, script);
+ assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1,c_op3", replaced);
+
+ // test form substitution with new forms
+ script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
+ "CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3}\n" +
+ "NEW_CHECKED=${checkbox( and ):new_check=nc_a|nc_c,nc_a|nc_b|nc_c}";
+ replaced = Input.getSimpleQuery(params, script);
+ assertEquals("INPUT=some_inputSELECTED=s_op2\nCHECKED=c_op1,c_op3\n" +
+ "NEW_CHECKED=nc_a and nc_c", replaced);
+
+ // test form substitution with obsoleted values
+ script = "INPUT=${input_form=}SELECTED=${select_form(Selection Form)=,s_op1|s_op2|s_op3}\n" +
+ "CHECKED=${checkbox:checkbox_form=c_op1|c_op2,c_op1|c_op2|c_op3_new}\n" +
+ "NEW_CHECKED=${checkbox( and ):new_check=nc_a|nc_c,nc_a|nc_b|nc_c}";
+ replaced = Input.getSimpleQuery(params, script);
+ 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/incubator-zeppelin/blob/4df083dc/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 bbcf764..8ecb3c4 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-parameterizedQueryForm.html
@@ -28,13 +28,24 @@ limitations under the License.
name="{{formulaire.name}}" />
<select class="form-control input-sm"
- ng-if="paragraph.settings.forms[formulaire.name].options"
+ ng-if="paragraph.settings.forms[formulaire.name].options && paragraph.settings.forms[formulaire.name].type != 'checkbox'"
ng-change="runParagraph(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 ng-if="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"
+ ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
+ ng-checked="paragraph.settings.params[formulaire.name].indexOf(option.value) > -1"
+ ng-click="toggleCheckbox(formulaire, option, false)"/>{{option.displayName||option.value}}
+ </label>
+ </div>
+
</div>
</div>
</form>
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 3b22b5b..3935cfc 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -629,13 +629,18 @@ angular.module('zeppelinWebApp')
value = params[formulaire.name];
}
- if (value === '') {
- value = formulaire.options[0].value;
- }
-
$scope.paragraph.settings.params[formulaire.name] = value;
};
+ $scope.toggleCheckbox = function(formulaire, option) {
+ var idx = $scope.paragraph.settings.params[formulaire.name].indexOf(option.value);
+ if (idx > -1) {
+ $scope.paragraph.settings.params[formulaire.name].splice(idx, 1);
+ } else {
+ $scope.paragraph.settings.params[formulaire.name].push(option.value);
+ }
+ };
+
$scope.aceChanged = function() {
$scope.dirtyText = $scope.editor.getSession().getValue();
$scope.startSaveTimer();
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/4df083dc/zeppelin-web/src/app/notebook/paragraph/paragraph.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
index 3b56c2a..f170ca0 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
@@ -234,6 +234,15 @@
padding-left: 0;
}
+.paragraphForm.form-horizontal .form-group .checkbox-item {
+ padding-left: 0;
+ padding-right: 10px;
+}
+
+.paragraphForm.form-horizontal .form-group .checkbox-item input {
+ margin-right: 2px;
+}
+
/*
Ace Text Editor CSS
*/