From 8c3f9fa16e0dc6ae3173bd6d475b38f512f0539d Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 31 Dec 2015 13:53:26 -0800 Subject: [PATCH 1/6] AngularFunction --- .../zeppelin/display/AngularFunction.java | 102 ++++++++++++++++++ .../display/AngularFunctionRunnable.java | 24 +++++ .../display/AngularObjectRegistry.java | 15 ++- .../src/app/notebook/notebook.controller.js | 32 ++++++ 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java create mode 100644 zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java new file mode 100644 index 00000000000..fc2b2f9d561 --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java @@ -0,0 +1,102 @@ +/* + * 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.Gson; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +/** + * AngularFunction provides proxy object in front-end side. + * Calling front-end side proxy will invoke AngularFunctionRunnable of this AngularFunction + */ +public class AngularFunction extends AngularObjectWatcher { + Logger logger = LoggerFactory.getLogger(AngularFunction.class); + private static final String ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = "_Z_ANGULAR_FUNC_"; + private final AngularObjectRegistry registry; + private final String name; + private final String noteId; + private final AngularFunctionRunnable runnable; + + // each invocation of from front-end proxy function will increase the counter + AngularObject counter; + // arguments of invocation from fron-end proxy function + AngularObject args; + // return value from AngularFunction to front-end proxy function + AngularObject ret; + + protected AngularFunction(AngularObjectRegistry registry, + String name, String noteId, + AngularFunctionRunnable runnable) { + super(null); + this.name = name; + this.noteId = noteId; + this.runnable = runnable; + this.registry = registry; + + remove(); + + ret = registry.add(getRetName(name), null, noteId); + args = registry.add(getArgsName(name), null, noteId); + counter = registry.add(getCounterName(name), 0, noteId); + counter.addWatcher(this); + } + + + static String getCounterName(String name) { + return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_" + name; + } + + static String getArgsName(String name) { + return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "ARGS_" + name; + } + + static String getRetName(String name) { + return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "RET_" + name; + } + + @Override + public void watch(Object oldObject, Object newObject, InterpreterContext context) { + if (runnable == null) { + return; + } + + Object argumentList = args.get(); + Object returnValue; + if (argumentList instanceof Object[]) { + returnValue = runnable.run((Object[]) args.get()); + } else { + returnValue = runnable.run(args.get()); + } + + ret.set(returnValue); + } + + + void remove() { + registry.remove(getCounterName(name), noteId); + registry.remove(getArgsName(name), noteId); + registry.remove(getRetName(name), noteId); + } + + public String getNoteId() { + return noteId; + } +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java new file mode 100644 index 00000000000..719e5bb2894 --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * User provided function body for AngularFunction + */ +public interface AngularFunctionRunnable { + public Object run (Object ... args); +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java index d6bab7b732c..f13a0677144 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java @@ -31,7 +31,7 @@ * - localRegistry: AngularObject is valid only inside of a single notebook */ public class AngularObjectRegistry { - Map> registry = + Map> registry = new HashMap>(); private final String GLOBAL_KEY = "_GLOBAL_"; private AngularObjectRegistryListener listener; @@ -69,6 +69,17 @@ public AngularObject add(String name, Object o, String noteId) { return add(name, o, noteId, true); } + public AngularFunction createAngularFunction(String name, String noteId, + AngularFunctionRunnable func) { + return new AngularFunction(this, name, noteId, func); + } + + public void removeAngularFunction(String name, String noteId) { + remove(AngularFunction.getCounterName(name), noteId); + remove(AngularFunction.getArgsName(name), noteId); + remove(AngularFunction.getRetName(name), noteId); + } + private String getRegistryKey(String noteId) { if (noteId == null) { return GLOBAL_KEY; @@ -87,7 +98,7 @@ private Map getRegistryForKey(String noteId) { return registry.get(key); } } - + public AngularObject add(String name, Object o, String noteId, boolean emit) { AngularObject ao = createNewAngularObject(name, o, noteId); diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 9030a49979d..fe6f486c839 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -18,6 +18,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, $rootScope, $http, websocketMsgSrv, baseUrlSrv, $timeout, SaveAsService) { + + var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = "_Z_ANGULAR_FUNC_"; $scope.note = null; $scope.showEditor = false; $scope.editorToggled = false; @@ -655,6 +657,24 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', }); } scope[varName] = data.angularObject.object; + + // create proxy for AngularFunction + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_")) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_").length); + scope[funcName] = function() { + var counterName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_" + funcName; + var argsName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "ARGS_" + funcName; + var retName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "RET_" + funcName; + + scope[argsName] = arguments; + scope[counterName] = scope[counterName] + 1; + + // TODO wait for return + console.log("angular function called"); + }; + + console.log("angular function created %o", scope[funcName]); + } } }); @@ -671,7 +691,19 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', // remove scope variable scope[varName] = undefined; + + // remove proxy for AngularFunction + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_")) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_").length); + scope[funcName] = undefined; + } } }); + $scope.$on('evaluateJs', function(event, data) { + if (!data.noteId || data.noteId === $scope.note.id) { + console.log("eval %o", data.script); + eval(data.script); + } + }); }); From 909106e75c24771806d160110b55aa20145423ee Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 31 Dec 2015 14:48:57 -0800 Subject: [PATCH 2/6] Use single angularobject for a angularfunction --- .../zeppelin/display/AngularFunction.java | 39 +++++-------------- .../display/AngularObjectRegistry.java | 4 +- .../src/app/notebook/notebook.controller.js | 26 +++---------- 3 files changed, 17 insertions(+), 52 deletions(-) diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java index fc2b2f9d561..f01018fa9b2 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunction.java @@ -35,12 +35,8 @@ public class AngularFunction extends AngularObjectWatcher { private final String noteId; private final AngularFunctionRunnable runnable; - // each invocation of from front-end proxy function will increase the counter - AngularObject counter; - // arguments of invocation from fron-end proxy function - AngularObject args; - // return value from AngularFunction to front-end proxy function - AngularObject ret; + // arguments of invocation from fron-end proxy function + AngularObject angularObject; protected AngularFunction(AngularObjectRegistry registry, String name, String noteId, @@ -53,23 +49,13 @@ protected AngularFunction(AngularObjectRegistry registry, remove(); - ret = registry.add(getRetName(name), null, noteId); - args = registry.add(getArgsName(name), null, noteId); - counter = registry.add(getCounterName(name), 0, noteId); - counter.addWatcher(this); + angularObject = registry.add(getFuncName(name), "", noteId); + angularObject.addWatcher(this); } - static String getCounterName(String name) { - return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_" + name; - } - - static String getArgsName(String name) { - return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "ARGS_" + name; - } - - static String getRetName(String name) { - return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "RET_" + name; + static String getFuncName(String name) { + return ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + name; } @Override @@ -78,22 +64,17 @@ public void watch(Object oldObject, Object newObject, InterpreterContext context return; } - Object argumentList = args.get(); - Object returnValue; + Object argumentList = angularObject.get(); if (argumentList instanceof Object[]) { - returnValue = runnable.run((Object[]) args.get()); + runnable.run((Object[]) angularObject.get()); } else { - returnValue = runnable.run(args.get()); + runnable.run(angularObject.get()); } - - ret.set(returnValue); } void remove() { - registry.remove(getCounterName(name), noteId); - registry.remove(getArgsName(name), noteId); - registry.remove(getRetName(name), noteId); + registry.remove(getFuncName(name), noteId); } public String getNoteId() { diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java index f13a0677144..fb83a37c034 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularObjectRegistry.java @@ -75,9 +75,7 @@ public AngularFunction createAngularFunction(String name, String noteId, } public void removeAngularFunction(String name, String noteId) { - remove(AngularFunction.getCounterName(name), noteId); - remove(AngularFunction.getArgsName(name), noteId); - remove(AngularFunction.getRetName(name), noteId); + remove(AngularFunction.getFuncName(name), noteId); } private String getRegistryKey(String noteId) { diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index fe6f486c839..5c2c238236c 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -659,18 +659,11 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', scope[varName] = data.angularObject.object; // create proxy for AngularFunction - if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_")) { - var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_").length); + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX)) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX).length); scope[funcName] = function() { - var counterName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_" + funcName; - var argsName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "ARGS_" + funcName; - var retName = ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "RET_" + funcName; - - scope[argsName] = arguments; - scope[counterName] = scope[counterName] + 1; - - // TODO wait for return - console.log("angular function called"); + scope[varName] = arguments; + console.log("angular function invoked %o", arguments); }; console.log("angular function created %o", scope[funcName]); @@ -693,17 +686,10 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', scope[varName] = undefined; // remove proxy for AngularFunction - if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_")) { - var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX + "COUNTER_").length); + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX)) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX).length); scope[funcName] = undefined; } } }); - - $scope.$on('evaluateJs', function(event, data) { - if (!data.noteId || data.noteId === $scope.note.id) { - console.log("eval %o", data.script); - eval(data.script); - } - }); }); From ba4ec8a277c82463c9d85a9aa6585fc49cbaba04 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 31 Dec 2015 16:01:35 -0800 Subject: [PATCH 3/6] Add quick access method to zeppeincontext --- .../zeppelin/spark/ZeppelinContext.java | 65 ++++++++++++++++--- .../display/AngularFunctionRunnable.java | 2 +- 2 files changed, 57 insertions(+), 10 deletions(-) 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 0d2d50c0e21..c9a11f5ea9f 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinContext.java @@ -24,21 +24,14 @@ import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import org.apache.spark.SparkContext; import org.apache.spark.sql.SQLContext; import org.apache.spark.sql.SQLContext.QueryExecution; import org.apache.spark.sql.catalyst.expressions.Attribute; import org.apache.spark.sql.hive.HiveContext; -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.*; import org.apache.zeppelin.display.Input.ParamOption; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterContextRunner; @@ -48,6 +41,9 @@ import scala.Tuple2; import scala.Unit; import scala.collection.Iterable; +import scala.collection.JavaConversions; +import scala.collection.Seq; +import scala.collection.mutable.Set; /** * Spark context for zeppelin. @@ -749,4 +745,55 @@ private void angularUnbind(String name, String noteId) { AngularObjectRegistry registry = interpreterContext.getAngularObjectRegistry(); registry.remove(name, noteId); } + + + public void angularBindFunction(String name, final scala.Function1, Unit> func) { + String noteId = interpreterContext.getNoteId(); + angularBindFunction(name, noteId, new AngularFunctionRunnable() { + @Override + public void run(Object... args) { + Set sets = JavaConversions.asScalaSet(new HashSet(Arrays.asList(args))); + func.apply(sets.toSeq()); + } + }); + } + + public void angularBindFunction(String name, AngularFunctionRunnable func) { + String noteId = interpreterContext.getNoteId(); + angularBindFunction(name, noteId, func); + } + + public void angularUnbindFunction(String name) { + String noteId = interpreterContext.getNoteId(); + angularUnbindFunction(name, noteId); + } + + public void angularBindFunctionGlobal(String name, + final scala.Function1, Unit> func) { + angularBindFunction(name, null, new AngularFunctionRunnable() { + @Override + public void run(Object... args) { + Set sets = JavaConversions.asScalaSet(new HashSet(Arrays.asList(args))); + func.apply(sets.toSeq()); + } + }); + } + + public void angularBindFunctionGlobal(String name, AngularFunctionRunnable func) { + angularBindFunction(name, null, func); + } + + public void angularUnbindFunctionGlobal(String name) { + angularUnbindFunction(name, null); + } + + private void angularBindFunction(String name, String noteId, AngularFunctionRunnable func) { + AngularObjectRegistry registry = interpreterContext.getAngularObjectRegistry(); + registry.createAngularFunction(name, noteId, func); + } + + private void angularUnbindFunction(String name, String noteId) { + AngularObjectRegistry registry = interpreterContext.getAngularObjectRegistry(); + registry.removeAngularFunction(name, noteId); + } } diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java index 719e5bb2894..43c8ff863d4 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/display/AngularFunctionRunnable.java @@ -20,5 +20,5 @@ * User provided function body for AngularFunction */ public interface AngularFunctionRunnable { - public Object run (Object ... args); + public void run (Object ... args); } From 3c7f8dce0f8ca48ed168d098d608db41524fa28f Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 31 Dec 2015 20:55:53 -0800 Subject: [PATCH 4/6] Singlequote --- zeppelin-web/src/app/notebook/notebook.controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 5c2c238236c..0e5216f1049 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -19,7 +19,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, $rootScope, $http, websocketMsgSrv, baseUrlSrv, $timeout, SaveAsService) { - var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = "_Z_ANGULAR_FUNC_"; + var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.note = null; $scope.showEditor = false; $scope.editorToggled = false; @@ -663,10 +663,10 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX).length); scope[funcName] = function() { scope[varName] = arguments; - console.log("angular function invoked %o", arguments); + console.log('angular function invoked %o', arguments); }; - console.log("angular function created %o", scope[funcName]); + console.log('angular function created %o', scope[funcName]); } } }); From b8c271944fd8a240639b105a65c5d97806e4b9f4 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Thu, 31 Dec 2015 20:56:01 -0800 Subject: [PATCH 5/6] Add test --- .../java/org/apache/zeppelin/ZeppelinIT.java | 152 ++++++++++++++++-- 1 file changed, 136 insertions(+), 16 deletions(-) diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinIT.java index 6b464dd3965..835e6896e61 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/ZeppelinIT.java @@ -18,6 +18,7 @@ package org.apache.zeppelin; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; @@ -99,7 +100,16 @@ boolean waitForParagraph(final int paragraphNo, final String state) { boolean waitForText(final String txt, final By locator) { try { WebElement element = pollingWait(locator, MAX_BROWSER_TIMEOUT_SEC); - return txt.equals(element.getText()); + FluentWait wait = new FluentWait(element) + .withTimeout(MAX_BROWSER_TIMEOUT_SEC, TimeUnit.SECONDS) + .pollingEvery(1, TimeUnit.SECONDS); + + return wait.until(new Function() { + @Override + public Boolean apply(WebElement webElement) { + return txt.equals(webElement.getText()); + } + }); } catch (TimeoutException e) { return false; } @@ -112,9 +122,9 @@ public WebElement pollingWait(final By locator, final long timeWait) { .ignoring(NoSuchElementException.class); return wait.until(new Function() { - public WebElement apply(WebDriver driver) { - return driver.findElement(locator); - } + public WebElement apply(WebDriver driver) { + return driver.findElement(locator); + } }); }; @@ -147,8 +157,8 @@ public void testAngularDisplay() throws InterruptedException{ waitForParagraph(1, "FINISHED"); // check expected text - waitForText("BindingTest__", By.xpath( - getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")); + assertTrue(waitForText("BindingTest__", By.xpath( + getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"))); /* * Bind variable @@ -161,8 +171,8 @@ public void testAngularDisplay() throws InterruptedException{ waitForParagraph(2, "FINISHED"); // check expected text - waitForText("BindingTest_1_", By.xpath( - getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")); + assertTrue(waitForText("BindingTest_1_", By.xpath( + getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"))); /* @@ -187,8 +197,8 @@ public void testAngularDisplay() throws InterruptedException{ getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")).click(); // check expected text - waitForText("BindingTest_2_", By.xpath( - getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")); + assertTrue(waitForText("BindingTest_2_", By.xpath( + getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"))); /* * Register watcher @@ -218,8 +228,8 @@ public void testAngularDisplay() throws InterruptedException{ waitForParagraph(3, "FINISHED"); // check expected text by watcher - waitForText("myVar=3", By.xpath( - getParagraphXPath(3) + "//div[@ng-bind=\"paragraph.result.msg\"]")); + assertTrue(waitForText("myVar=3", By.xpath( + getParagraphXPath(3) + "//div[@ng-bind=\"paragraph.result.msg\"]"))); /* * Unbind @@ -232,8 +242,8 @@ public void testAngularDisplay() throws InterruptedException{ waitForParagraph(5, "FINISHED"); // check expected text - waitForText("BindingTest__", - By.xpath(getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")); + assertTrue(waitForText("BindingTest__", + By.xpath(getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"))); /* * Bind again and see rebind works. @@ -243,8 +253,8 @@ public void testAngularDisplay() throws InterruptedException{ waitForParagraph(2, "FINISHED"); // check expected text - waitForText("BindingTest_1_", - By.xpath(getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")); + assertTrue(waitForText("BindingTest_1_", + By.xpath(getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"))); driver.findElement(By.xpath("//*[@id='main']/div//h3/span[1]/button[@tooltip='Remove the notebook']")) .sendKeys(Keys.ENTER); @@ -256,7 +266,117 @@ public void testAngularDisplay() throws InterruptedException{ System.out.println("testCreateNotebook Test executed"); } catch (ElementNotVisibleException e) { File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); + throw e; + } + } + + + @Test + public void testAngularFunction() throws InterruptedException{ + if (!endToEndTestEnabled()) { + return; + } + try { + createNewNote(); + + // wait for first paragraph's " READY " status text + waitForParagraph(1, "READY"); + + /* + * print angular template + * %angular
BindingTest_{{myVar}}_
+ */ + WebElement paragraph1Editor = driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")); + paragraph1Editor.sendKeys("println" + Keys.chord(Keys.SHIFT, "9") + "\"" + + Keys.chord(Keys.SHIFT, "5") + + "angular
" + + "FunctionTest
\")"); + paragraph1Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(1, "FINISHED"); + /* + * Bind variable + * var a=1 + * z.angularFunction("myFunc", { args: Seq[Object] => a = a + 1 }) + */ + assertEquals(1, driver.findElements(By.xpath(getParagraphXPath(2) + "//textarea")).size()); + WebElement paragraph2Editor = driver.findElement(By.xpath(getParagraphXPath(2) + "//textarea")); + paragraph2Editor.sendKeys("var a=1; z.angularBindFunction" + Keys.chord(Keys.SHIFT, "9") + "\"myFunc\", " + + "{ args: Seq[Object] => a = a " + Keys.chord(Keys.ADD) + " 1 })"); + paragraph2Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(2, "FINISHED"); + + /* + * print variable + * print(a) + */ + WebElement paragraph3Editor = driver.findElement(By.xpath(getParagraphXPath(3) + "//textarea")); + paragraph3Editor.sendKeys("print" + Keys.chord(Keys.SHIFT, "9") + "a)"); + paragraph3Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(3, "FINISHED"); + + // check expected text + assertTrue(waitForText("1", By.xpath( + getParagraphXPath(3) + "//div[@ng-bind=\"paragraph.result.msg\"]"))); + + + /* + * Click button + */ + driver.findElement(By.xpath( + getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")).click(); + + /** + * Run paragarph3 + */ + paragraph3Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(3, "FINISHED"); + + // check expected text + assertTrue(waitForText("2", By.xpath( + getParagraphXPath(3) + "//div[@ng-bind=\"paragraph.result.msg\"]"))); + + + /* + * unbind function + * z.angularUnbindFunction("myFunc") + */ + WebElement paragraph4Editor = driver.findElement(By.xpath(getParagraphXPath(4) + "//textarea")); + paragraph4Editor.sendKeys("z.angularUnbindFunction" + Keys.chord(Keys.SHIFT, "9") + "\"myFunc\")"); + paragraph4Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(4, "FINISHED"); + + + /* + * Click button + */ + driver.findElement(By.xpath( + getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]")).click(); + + /** + * Run paragarph3 + */ + paragraph3Editor.sendKeys(Keys.chord(Keys.SHIFT, Keys.ENTER)); + waitForParagraph(3, "FINISHED"); + + // check expected text + assertTrue(waitForText("2", By.xpath( + getParagraphXPath(3) + "//div[@ng-bind=\"paragraph.result.msg\"]"))); + + + // remove notebook + driver.findElement(By.xpath("//*[@id='main']/div//h3/span[1]/button[@tooltip='Remove the notebook']")) + .sendKeys(Keys.ENTER); + ZeppelinITUtils.sleep(1000, true); + driver.findElement(By.xpath("//div[@class='modal-dialog'][contains(.,'delete this notebook')]" + + "//div[@class='modal-footer']//button[contains(.,'OK')]")).click(); + ZeppelinITUtils.sleep(100, true); + + System.out.println("testCreateNotebook Test executed"); + } catch (ElementNotVisibleException e) { + File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); + throw e; } } From 9be410f0fac295361accdb57ce8e4d95a940f906 Mon Sep 17 00:00:00 2001 From: Lee moon soo Date: Fri, 1 Jan 2016 00:37:34 -0800 Subject: [PATCH 6/6] Add docs for angular function --- docs/displaysystem/angular.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/displaysystem/angular.md b/docs/displaysystem/angular.md index c8a0ddc1542..c1752dce110 100644 --- a/docs/displaysystem/angular.md +++ b/docs/displaysystem/angular.md @@ -96,3 +96,28 @@ z.angularBind("run", 0) will initialize 'run' to zero. And then register watcher After clicked button, you'll see both 'run' and numWatched are increased by 1 + + + +
+#### Bind/Unbind function + +Through ZeppelinContext, you can bind/unbind function to AngularJS view. + +Currently it only works in Spark Interpreter (scala). + +``` +// bind function in current notebook. +z.angularBindFunction(String name, { args: Seq[Object] => ... }) + +// bind function in all notebooks related to current interpreter. +z.angularBindGlobal(String name, Object object) + +// unbind function in current notebook. +z.angularUnbind(String name) + +// unbind function in all notebooks related to current interpreter. +z.angularUnbindGlobal(String name) + +``` +