Repository: zeppelin Updated Branches: refs/heads/master 5766c272b -> 5e33f2c98
ZEPPELIN-2528. Add a password text input to the ZeppelinContext ### What is this PR for? This is to add password for dynamic forms. It is almost the same as input box but but with invisible input. And there's no default value for password form, as it doesn't make sense to provide default value for password. ### What type of PR is it? [ Feature ] ### Todos * [ ] - Task ### What is the Jira issue? * https://issues.apache.org/jira/browse/ZEPPELIN-2528 ### How should this be tested? * CI pass ### Screenshots (if appropriate) ![form_password_prog](https://user-images.githubusercontent.com/164491/41765448-aee6188e-7636-11e8-867b-ad766ee4188f.png) ![form_password](https://user-images.githubusercontent.com/164491/41765449-af151ba2-7636-11e8-8f2b-6f16dfde96b7.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang <zjf...@apache.org> Closes #3041 from zjffdu/ZEPPELIN-2528 and squashes the following commits: 084abd39c [Jeff Zhang] ZEPPELIN-2528. Add a password text input to the ZeppelinContext Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/5e33f2c9 Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/5e33f2c9 Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/5e33f2c9 Branch: refs/heads/master Commit: 5e33f2c98d3b43b81df3d13d597d739b264405ec Parents: 5766c27 Author: Jeff Zhang <zjf...@apache.org> Authored: Fri Jun 22 14:37:17 2018 +0800 Committer: Jeff Zhang <zjf...@apache.org> Committed: Thu Jun 28 09:56:48 2018 +0800 ---------------------------------------------------------------------- .travis.yml | 2 +- .../zeppelin/img/screenshots/form_password.png | Bin 0 -> 29067 bytes .../img/screenshots/form_password_prog.png | Bin 0 -> 34717 bytes docs/usage/dynamic_form/intro.md | 30 ++++++++++++++++ .../main/resources/python/zeppelin_context.py | 3 ++ .../python/BasePythonInterpreterTest.java | 11 ++++++ .../zeppelin/spark/NewSparkInterpreterTest.java | 9 +++++ .../java/org/apache/zeppelin/display/GUI.java | 6 ++++ .../java/org/apache/zeppelin/display/Input.java | 4 +++ .../apache/zeppelin/display/ui/Password.java | 36 +++++++++++++++++++ .../interpreter/BaseZeppelinContext.java | 14 ++++++++ .../org/apache/zeppelin/display/InputTest.java | 20 +++++------ .../zeppelin/rest/ZeppelinSparkClusterTest.java | 32 ++++++++++------- .../dynamic-forms/dynamic-forms.directive.html | 19 ++++++++++ 14 files changed, 162 insertions(+), 24 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/.travis.yml ---------------------------------------------------------------------- diff --git a/.travis.yml b/.travis.yml index 1227876..7c10987 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ matrix: dist: trusty addons: firefox: "31.0" - env: BUILD_PLUGINS="true" CI="true" PYTHON="2" SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pspark-1.6 -Phadoop2 -Phelium-dev -Pexamples -Pintegration -Pscala-2.10" BUILD_FLAG="install -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl .,zeppelin-integration -DfailIfNoTests=false" + env: BUILD_PLUGINS="true" CI="true" PYTHON="2" SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pspark-1.6 -Phadoop2 -Phelium-dev -Pexamples -Pintegration -Pscala-2.10" BUILD_FLAG="install -DskipTests -DskipRat -pl ${INTERPRETERS}" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl zeppelin-integration -DfailIfNoTests=false" # Test interpreter modules - jdk: "oraclejdk8" http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/docs/assets/themes/zeppelin/img/screenshots/form_password.png ---------------------------------------------------------------------- diff --git a/docs/assets/themes/zeppelin/img/screenshots/form_password.png b/docs/assets/themes/zeppelin/img/screenshots/form_password.png new file mode 100644 index 0000000..5b4eb72 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/form_password.png differ http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/docs/assets/themes/zeppelin/img/screenshots/form_password_prog.png ---------------------------------------------------------------------- diff --git a/docs/assets/themes/zeppelin/img/screenshots/form_password_prog.png b/docs/assets/themes/zeppelin/img/screenshots/form_password_prog.png new file mode 100644 index 0000000..40f15e6 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/screenshots/form_password_prog.png differ http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/docs/usage/dynamic_form/intro.md ---------------------------------------------------------------------- diff --git a/docs/usage/dynamic_form/intro.md b/docs/usage/dynamic_form/intro.md index 1da5876..3f1b9fe 100644 --- a/docs/usage/dynamic_form/intro.md +++ b/docs/usage/dynamic_form/intro.md @@ -44,6 +44,15 @@ Also you can provide default value, using `${formName=defaultValue}`. <img src="{{BASE_PATH}}/assets/themes/zeppelin/img/screenshots/form_input_default.png" /> +### Password form + +To create password form, use `${password:formName}` templates. + +for example + +<img class="img-responsive" src="{{BASE_PATH}}/assets/themes/zeppelin/img/screenshots/form_password.png" /> + + ### Select form To create select form, use `${formName=defaultValue,option1|option2...}` @@ -134,6 +143,27 @@ print("Hello "+z.textbox("name", "sun")) </div> <img src="{{BASE_PATH}}/assets/themes/zeppelin/img/screenshots/form_input_default_prog.png" /> +### Password form +<div class="codetabs"> + <div data-lang="scala" markdown="1"> + +{% highlight scala %} +%spark +print("Password is "+ z.password("my_password")) +{% endhighlight %} + + </div> + <div data-lang="python" markdown="1"> + +{% highlight python %} +%pyspark +print("Password is "+ z.password("my_password")) +{% endhighlight %} + + </div> +</div> +<img src="{{BASE_PATH}}/assets/themes/zeppelin/img/screenshots/form_password_prog.png" /> + ### Select form <div class="codetabs"> <div data-lang="scala" markdown="1"> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/python/src/main/resources/python/zeppelin_context.py ---------------------------------------------------------------------- diff --git a/python/src/main/resources/python/zeppelin_context.py b/python/src/main/resources/python/zeppelin_context.py index d29a16f..dc97c14 100644 --- a/python/src/main/resources/python/zeppelin_context.py +++ b/python/src/main/resources/python/zeppelin_context.py @@ -70,6 +70,9 @@ class PyZeppelinContext(object): def textbox(self, name, defaultValue=""): return self.z.textbox(name, defaultValue) + def password(self, name): + return self.z.password(name) + def noteTextbox(self, name, defaultValue=""): return self.z.noteTextbox(name, defaultValue) http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/python/src/test/java/org/apache/zeppelin/python/BasePythonInterpreterTest.java ---------------------------------------------------------------------- diff --git a/python/src/test/java/org/apache/zeppelin/python/BasePythonInterpreterTest.java b/python/src/test/java/org/apache/zeppelin/python/BasePythonInterpreterTest.java index 9697fbf..25cb253 100644 --- a/python/src/test/java/org/apache/zeppelin/python/BasePythonInterpreterTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/BasePythonInterpreterTest.java @@ -18,6 +18,7 @@ package org.apache.zeppelin.python; import org.apache.zeppelin.display.ui.CheckBox; +import org.apache.zeppelin.display.ui.Password; import org.apache.zeppelin.display.ui.Select; import org.apache.zeppelin.display.ui.TextBox; import org.apache.zeppelin.interpreter.Interpreter; @@ -240,6 +241,16 @@ public abstract class BasePythonInterpreterTest { assertEquals("text_1", textbox.getName()); assertEquals("value_1", textbox.getDefaultValue()); + // Password + context = getInterpreterContext(); + result = + interpreter.interpret("z.password(name='pwd_1')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + assertTrue(context.getGui().getForms().get("pwd_1") instanceof Password); + Password password = (Password) context.getGui().getForms().get("pwd_1"); + assertEquals("pwd_1", password.getName()); + // Select context = getInterpreterContext(); result = interpreter.interpret("z.select(name='select_1'," + http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/spark/interpreter/src/test/java/org/apache/zeppelin/spark/NewSparkInterpreterTest.java ---------------------------------------------------------------------- diff --git a/spark/interpreter/src/test/java/org/apache/zeppelin/spark/NewSparkInterpreterTest.java b/spark/interpreter/src/test/java/org/apache/zeppelin/spark/NewSparkInterpreterTest.java index 65b739d..73bd52c 100644 --- a/spark/interpreter/src/test/java/org/apache/zeppelin/spark/NewSparkInterpreterTest.java +++ b/spark/interpreter/src/test/java/org/apache/zeppelin/spark/NewSparkInterpreterTest.java @@ -20,6 +20,7 @@ package org.apache.zeppelin.spark; import com.google.common.io.Files; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.ui.CheckBox; +import org.apache.zeppelin.display.ui.Password; import org.apache.zeppelin.display.ui.Select; import org.apache.zeppelin.display.ui.TextBox; import org.apache.zeppelin.interpreter.Interpreter; @@ -216,6 +217,14 @@ public class NewSparkInterpreterTest { assertEquals("default_name", textBox.getDefaultValue()); context = getInterpreterContext(); + result = interpreter.interpret("z.password(\"pwd\")", context); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + assertEquals(1, context.getGui().getForms().size()); + assertTrue(context.getGui().getForms().get("pwd") instanceof Password); + Password pwd = (Password) context.getGui().getForms().get("pwd"); + assertEquals("pwd", pwd.getName()); + + context = getInterpreterContext(); result = interpreter.interpret("z.checkbox(\"checkbox_1\", Seq(\"value_2\"), Seq((\"value_1\", \"name_1\"), (\"value_2\", \"name_2\")))", context); assertEquals(InterpreterResult.Code.SUCCESS, result.code()); assertEquals(1, context.getGui().getForms().size()); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/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 5657c58..048ee81 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 @@ -21,6 +21,7 @@ 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.Password; import org.apache.zeppelin.display.ui.Select; import org.apache.zeppelin.display.ui.TextBox; @@ -90,6 +91,11 @@ public class GUI implements Serializable { return textbox(id, ""); } + public Object password(String id) { + forms.put(id, new Password(id)); + return params.get(id); + } + public Object select(String id, Object defaultValue, ParamOption[] options) { if (defaultValue == null && options != null && options.length > 0) { defaultValue = options[0].getValue(); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/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 51e27d2..40878a8 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 @@ -21,6 +21,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.display.ui.CheckBox; import org.apache.zeppelin.display.ui.OptionInput; import org.apache.zeppelin.display.ui.OptionInput.ParamOption; +import org.apache.zeppelin.display.ui.Password; import org.apache.zeppelin.display.ui.Select; import org.apache.zeppelin.display.ui.TextBox; @@ -49,6 +50,7 @@ public class Input<T> implements Serializable { .registerSubtype(TextBox.class, "TextBox") .registerSubtype(Select.class, "Select") .registerSubtype(CheckBox.class, "CheckBox") + .registerSubtype(Password.class, "Password") .registerSubtype(OldInput.OldTextBox.class, "input") .registerSubtype(OldInput.OldSelect.class, "select") .registerSubtype(OldInput.OldCheckBox.class, "checkbox") @@ -282,6 +284,8 @@ public class Input<T> implements Serializable { } } else if (type.equals("checkbox")) { input = new CheckBox(varName, (Object[]) defaultValue, paramOptions); + } else if (type.equals("password")) { + input = new Password(varName); } else { throw new RuntimeException("Could not recognize dynamic form with type: " + type); } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Password.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Password.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Password.java new file mode 100644 index 0000000..e3fd624 --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/ui/Password.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; + +import org.apache.zeppelin.display.Input; + +public class Password extends Input<String> { + + public Password() { + + } + + public Password(String name) { + this.name = name; + this.displayName = name; + this.defaultValue = ""; + } + +} + http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/BaseZeppelinContext.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/BaseZeppelinContext.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/BaseZeppelinContext.java index dba9471..04f6c70 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/BaseZeppelinContext.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/BaseZeppelinContext.java @@ -103,6 +103,20 @@ public abstract class BaseZeppelinContext { } @ZeppelinApi + public Object password(String name) { + return password(name, false); + } + + @ZeppelinApi + public Object password(String name, boolean noteForm) { + if (noteForm) { + return noteGui.password(name); + } else { + return gui.password(name); + } + } + + @ZeppelinApi public Collection<Object> checkbox(String name, ParamOption[] options) { return checkbox(name, options, false); } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/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 a9252b9..abe2ac3 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 @@ -19,10 +19,9 @@ package org.apache.zeppelin.display; import org.apache.zeppelin.display.ui.CheckBox; import org.apache.zeppelin.display.ui.OptionInput.ParamOption; +import org.apache.zeppelin.display.ui.Password; 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 java.util.HashMap; @@ -35,14 +34,6 @@ import static org.junit.Assert.assertTrue; public class InputTest { - @Before - public void setUp() throws Exception { - } - - @After - public void tearDown() throws Exception { - } - @Test public void testFormExtraction() { // textbox form @@ -61,12 +52,21 @@ public class InputTest { form = forms.get("input_form"); assertEquals("xxx", form.defaultValue); assertTrue(form instanceof TextBox); + assertEquals("Input Form", form.getDisplayName()); + + // password form with display name + script = "${password:my_pwd(My Password)}"; + forms = Input.extractSimpleQueryForm(script, false); + form = forms.get("my_pwd"); + assertTrue(form instanceof Password); + assertEquals("My Password", form.getDisplayName()); // selection form script = "${select_form(Selection Form)=op1,op1|op2(Option 2)|op3}"; form = Input.extractSimpleQueryForm(script, false).get("select_form"); assertEquals("select_form", form.name); assertEquals("op1", form.defaultValue); + assertEquals("Selection Form", form.getDisplayName()); assertTrue(form instanceof Select); assertArrayEquals(new ParamOption[]{ new ParamOption("op1", null), http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/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 edfbc65..fdb41a4 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 @@ -499,6 +499,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi { Note note = ZeppelinServer.notebook.createNote(anonymous); Paragraph p = note.addNewParagraph(anonymous); String code = "%spark.spark println(z.textbox(\"my_input\", \"default_name\"))\n" + + "println(z.password(\"my_pwd\"))\n" + "println(z.select(\"my_select\", \"1\"," + "Seq((\"1\", \"select_1\"), (\"2\", \"select_2\"))))\n" + "val items=z.checkbox(\"my_checkbox\", Seq(\"2\"), " + @@ -510,17 +511,19 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi { assertEquals(Status.FINISHED, p.getStatus()); Iterator<String> formIter = p.settings.getForms().keySet().iterator(); - assert (formIter.next().equals("my_input")); - assert (formIter.next().equals("my_select")); - assert (formIter.next().equals("my_checkbox")); + assertEquals("my_input", formIter.next()); + assertEquals("my_pwd", formIter.next()); + assertEquals("my_select", formIter.next()); + assertEquals("my_checkbox", formIter.next()); // check dynamic forms values String[] result = p.getResult().message().get(0).getData().split("\n"); - assertEquals(4, result.length); + assertEquals(5, result.length); assertEquals("default_name", result[0]); - assertEquals("1", result[1]); - assertEquals("items: Seq[Object] = Buffer(2)", result[2]); - assertEquals("2", result[3]); + assertEquals("null", result[1]); + assertEquals("1", result[2]); + assertEquals("items: Seq[Object] = Buffer(2)", result[3]); + assertEquals("2", result[4]); } @Test @@ -528,6 +531,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi { Note note = ZeppelinServer.notebook.createNote(anonymous); Paragraph p = note.addNewParagraph(anonymous); String code = "%spark.pyspark print(z.input('my_input', 'default_name'))\n" + + "print(z.password('my_pwd'))\n" + "print(z.select('my_select', " + "[('1', 'select_1'), ('2', 'select_2')], defaultValue='1'))\n" + "items=z.checkbox('my_checkbox', " + @@ -538,16 +542,18 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi { assertEquals(Status.FINISHED, p.getStatus()); Iterator<String> formIter = p.settings.getForms().keySet().iterator(); - assert (formIter.next().equals("my_input")); - assert (formIter.next().equals("my_select")); - assert (formIter.next().equals("my_checkbox")); + assertEquals("my_input", formIter.next()); + assertEquals("my_pwd", formIter.next()); + assertEquals("my_select", formIter.next()); + assertEquals("my_checkbox", formIter.next()); // check dynamic forms values String[] result = p.getResult().message().get(0).getData().split("\n"); - assertEquals(3, result.length); + assertEquals(4, result.length); assertEquals("default_name", result[0]); - assertEquals("1", result[1]); - assertEquals("2", result[2]); + assertEquals("None", result[1]); + assertEquals("1", result[2]); + assertEquals("2", result[3]); } @Test http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5e33f2c9/zeppelin-web/src/app/notebook/dynamic-forms/dynamic-forms.directive.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/dynamic-forms/dynamic-forms.directive.html b/zeppelin-web/src/app/notebook/dynamic-forms/dynamic-forms.directive.html index f9f6028..e73f9f8 100644 --- a/zeppelin-web/src/app/notebook/dynamic-forms/dynamic-forms.directive.html +++ b/zeppelin-web/src/app/notebook/dynamic-forms/dynamic-forms.directive.html @@ -42,6 +42,25 @@ limitations under the License. name="{{formulaire.name}}" /> </div> <div ng-if="actiononchange === true"> + <input class="form-control input-sm" + ng-if="forms[formulaire.name].type == 'Password'" + ng-change="action()" + ng-model-options='{ debounce: 1000 }' + ng-model="params[formulaire.name]" + ng-class="{'disable': disable}" + type="password" + name="{{formulaire.name}}" /> + </div> + <div ng-if="!actiononchange"> + <input class="form-control input-sm" + ng-if="forms[formulaire.name].type == 'Password'" + ng-enter="action()" + ng-model="params[formulaire.name]" + ng-class="{'disable': disable}" + type="password" + name="{{formulaire.name}}" /> + </div> + <div ng-if="actiononchange === true"> <select class="form-control input-sm" ng-if="forms[formulaire.name].type == 'Select'" ng-change="action()"