Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master 566ffd0b9 -> 140adb8d3


[ZEPPELIN-695] Add AngularJS z.runParagraph()

### What is this PR for?
Add AngularJS `z.runParagraph()` method

corneadoug
On the client-side, inside the scope of a paragraph, we cannot access to the 
list of all existing paragraphs for the current note. In my previous 
implementation, I was using the following trick:

```
var paragraphDiv = angular.element('#' + paragraphId +
                                
'_paragraphColumn_main[ng-controller="ParagraphCtrl"]');

var paragraph = paragraphDiv.scope().paragraph;
```

This is **dirty** and can be broken if we change the CSS style tomorrow for 
paragraph.

For the current implementation, what I did is to **inject** a reference to the 
`note` object into the `$scope` of paragraph so that we can access the current 
list of paragraph **programmatically**:

```javascript
        $scope.$broadcast('updateParagraph', {
          note: $scope.note, // pass the note object to paragraph scope
          paragraph: note.paragraphs[index]});
```

```html
  <div id="{{currentParagraph.id}}_paragraphColumn_main"
       ng-repeat="currentParagraph in note.paragraphs"
       ng-controller="ParagraphCtrl"
       ng-Init="init(currentParagraph, note)"
```

_This is a sub-task of epic **[ZEPPELIN-635]**_

### What type of PR is it?
[Improvement### Todos
* [ ] - Code Review
* [ ] - Simple Test

### Is there a relevant Jira issue?
**[ZEPPELIN-695]**

### How should this be tested?
* `git fetch origin pull/742/head:AngularJSRunParagraph`
* `git checkout AngularJSRunParagraph`
* `mvn clean package -DskipTests`
* `bin/zeppelin-daemon.sh restart`
* Create a new note
* In the first paragraph, put the following code

```html
%angular

<form class="form-inline">
  <div class="form-group">
    <label for="paragraphId">Paragraph Id: </label>
    <input type="text" class="form-control" id="paragraphId" 
placeholder="Paragraph Id ..." ng-model="paragraph"></input>
  </div>
  <button type="submit" class="btn btn-primary" 
ng-click="z.runParagraph(paragraph)"> Run Paragraph</button>
</form>
```
* Create a second paragraph with the following code:

```scala
println("Date "+new java.util.Date().toString)
```
* Retrieve the paragraph id of the 2nd paragraph
* In the first paragraph, put the paragraph id in the input text and click on 
the **Run Paragraph** button, it should trigger execution of the second 
paragraph

### Screenshots (if appropriate)
![angularrunparagraph](https://cloud.githubusercontent.com/assets/1532977/13898909/ee3767bc-ede0-11e5-9996-79aa3b5c465c.gif)

### Questions:
* Does the licenses files need update? --> **No**
* Is there breaking changes for older versions? --> **No**
* Does this needs documentation? --> **Yes**

[ZEPPELIN-635]: https://issues.apache.org/jira/browse/ZEPPELIN-635
[ZEPPELIN-695]: https://issues.apache.org/jira/browse/ZEPPELIN-695

Author: DuyHai DOAN <[email protected]>

Closes #742 from doanduyhai/ZEPPELIN-695 and squashes the following commits:

cf0e6e4 [DuyHai DOAN] [ZEPPELIN-695] Add AngularJS z.runParagraph()


Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/140adb8d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/140adb8d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/140adb8d

Branch: refs/heads/master
Commit: 140adb8d3939aef850b646bbecbc7b63e0b99f94
Parents: 566ffd0
Author: DuyHai DOAN <[email protected]>
Authored: Sat Mar 19 14:33:41 2016 +0100
Committer: Lee moon soo <[email protected]>
Committed: Sat Mar 26 16:19:00 2016 -0700

----------------------------------------------------------------------
 .../apache/zeppelin/integration/ZeppelinIT.java | 72 ++++++++++++++++++--
 zeppelin-web/src/app/home/home.html             |  2 +-
 .../src/app/notebook/notebook.controller.js     |  8 ++-
 zeppelin-web/src/app/notebook/notebook.html     |  4 +-
 .../notebook/paragraph/paragraph.controller.js  | 19 +++++-
 5 files changed, 95 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/140adb8d/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ZeppelinIT.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ZeppelinIT.java 
b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ZeppelinIT.java
index d7f3b49..bad8b84 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ZeppelinIT.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/ZeppelinIT.java
@@ -17,13 +17,11 @@
 
 package org.apache.zeppelin.integration;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.zeppelin.AbstractZeppelinIT;
 import org.apache.zeppelin.WebDriverManager;
 import org.hamcrest.CoreMatchers;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.*;
 import org.junit.rules.ErrorCollector;
 import org.openqa.selenium.By;
 import org.openqa.selenium.Keys;
@@ -31,7 +29,9 @@ import org.openqa.selenium.WebElement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Test Zeppelin with web browser.
@@ -246,4 +246,68 @@ public class ZeppelinIT extends AbstractZeppelinIT {
       handleException("Exception in ZeppelinIT while 
testSparkInterpreterDependencyLoading ", e);
     }
   }
+
+  @Test
+  public void testAngularRunParagraph() throws Exception {
+    if (!endToEndTestEnabled()) {
+      return;
+    }
+
+    try {
+      createNewNote();
+
+      // wait for first paragraph's " READY " status text
+      waitForParagraph(1, "READY");
+
+      // Create 1st paragraph
+      setTextOfParagraph(1,
+              "%angular <div id=\\'angularRunParagraph\\'>Run second 
paragraph</div>");
+      runParagraph(1);
+      waitForParagraph(1, "FINISHED");
+      waitForText("Run second paragraph", By.xpath(
+              getParagraphXPath(1) + "//div[@id=\"angularRunParagraph\"]"));
+
+      // Create 2nd paragraph
+      setTextOfParagraph(2, "%sh echo TEST");
+      runParagraph(2);
+      waitForParagraph(2, "FINISHED");
+
+      // Get 2nd paragraph id
+      final String secondParagraphId = 
driver.findElement(By.xpath(getParagraphXPath(2)
+              + "//div[@class=\"control 
ng-scope\"]//ul[@class=\"dropdown-menu\"]/li[1]"))
+              .getAttribute("textContent");
+
+      assertTrue("Cannot find paragraph id for the 2nd paragraph", 
isNotBlank(secondParagraphId));
+
+      // Update first paragraph to call z.runParagraph() with 2nd paragraph id
+      setTextOfParagraph(1,
+              "%angular <div id=\\'angularRunParagraph\\' 
ng-click=\\'z.runParagraph(\""
+                      + secondParagraphId.trim()
+                      + "\")\\'>Run second paragraph</div>");
+      runParagraph(1);
+      waitForParagraph(1, "FINISHED");
+
+      // Set new text value for 2nd paragraph
+      setTextOfParagraph(2, "%sh echo NEW_VALUE");
+
+      // Click on 1 paragraph to trigger z.runParagraph() function
+      driver.findElement(By.xpath(
+              getParagraphXPath(1) + 
"//div[@id=\"angularRunParagraph\"]")).click();
+
+      waitForParagraph(2, "FINISHED");
+
+      // Check that 2nd paragraph has been executed
+      waitForText("NEW_VALUE", By.xpath(
+              getParagraphXPath(2) + "//div[contains(@id,\"_text\") and 
@class=\"text\"]"));
+
+      //delete created notebook for cleanup.
+      deleteTestNotebook(driver);
+      sleep(1000, true);
+
+      LOG.info("testAngularRunParagraph Test executed");
+    }  catch (Exception e) {
+      handleException("Exception in ZeppelinIT while testAngularRunParagraph", 
e);
+    }
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/140adb8d/zeppelin-web/src/app/home/home.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.html 
b/zeppelin-web/src/app/home/home.html
index 439dfa6..5eff5e0 100644
--- a/zeppelin-web/src/app/home/home.html
+++ b/zeppelin-web/src/app/home/home.html
@@ -72,7 +72,7 @@ limitations under the License.
   <div ng-show="home.notebookHome" 
id="{{currentParagraph.id}}_paragraphColumn_main"
        ng-repeat="currentParagraph in home.note.paragraphs"
        ng-controller="ParagraphCtrl"
-       ng-Init="init(currentParagraph)"
+       ng-Init="init(currentParagraph, home.note)"
        ng-class="columnWidthClass(currentParagraph.config.colWidth)"
        class="paragraph-col">
     <div id="{{currentParagraph.id}}_paragraphColumn"

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/140adb8d/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js 
b/zeppelin-web/src/app/notebook/notebook.controller.js
index 194cd77..fb1120e 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -488,7 +488,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
           paragraphToBeFocused = note.paragraphs[index].id;
           break;
         }
-        $scope.$broadcast('updateParagraph', {paragraph: 
note.paragraphs[index]});
+        $scope.$broadcast('updateParagraph', {
+          note: $scope.note, // pass the note object to paragraph scope
+          paragraph: note.paragraphs[index]});
       }
     }
 
@@ -497,7 +499,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
       for (var idx in newParagraphIds) {
         var newEntry = note.paragraphs[idx];
         if (oldParagraphIds[idx] === newParagraphIds[idx]) {
-          $scope.$broadcast('updateParagraph', {paragraph: newEntry});
+          $scope.$broadcast('updateParagraph', {
+            note: $scope.note, // pass the note object to paragraph scope
+            paragraph: newEntry});
         } else {
           // move paragraph
           var oldIdx = oldParagraphIds.indexOf(newParagraphIds[idx]);

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/140adb8d/zeppelin-web/src/app/notebook/notebook.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.html 
b/zeppelin-web/src/app/notebook/notebook.html
index f2e2bb4..d4b72bc 100644
--- a/zeppelin-web/src/app/notebook/notebook.html
+++ b/zeppelin-web/src/app/notebook/notebook.html
@@ -84,11 +84,11 @@ limitations under the License.
 
   <div class="note-jump"></div>
 
-  <!-- Include the paragraphs according to the note -->
+  <!-- Include the paragraphs according to the note, pass the note to init 
function -->
   <div id="{{currentParagraph.id}}_paragraphColumn_main"
        ng-repeat="currentParagraph in note.paragraphs"
        ng-controller="ParagraphCtrl"
-       ng-Init="init(currentParagraph)"
+       ng-Init="init(currentParagraph, note)"
        ng-class="columnWidthClass(currentParagraph.config.colWidth)"
        class="paragraph-col">
     <div class="new-paragraph" ng-click="insertNew('above')" ng-hide="viewOnly 
|| asIframe">

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/140adb8d/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 a6489fd..bb9bc89 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -18,6 +18,7 @@ angular.module('zeppelinWebApp')
   .controller('ParagraphCtrl', function($scope,$rootScope, $route, $window, 
$element, $routeParams, $location,
                                          $timeout, $compile, websocketMsgSrv) {
   var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
+  $scope.parentNote = null;
   $scope.paragraph = null;
   $scope.originalText = '';
   $scope.editor = null;
@@ -28,6 +29,20 @@ angular.module('zeppelinWebApp')
   $scope.compiledScope = paragraphScope;
 
   paragraphScope.z = {
+    // z.runParagraph('20150213-231621_168813393')
+    runParagraph: function(paragraphId) {
+      if (paragraphId) {
+        var filtered = $scope.parentNote.paragraphs.filter(function(x) {
+          return x.id === paragraphId;});
+        if (filtered.length === 1) {
+          var paragraph = filtered[0];
+          websocketMsgSrv.runParagraph(paragraph.id, paragraph.title, 
paragraph.text,
+              paragraph.config, paragraph.settings.params);
+        } else {
+          // Error message here
+        }
+      }
+    },
 
     // Example: z.angularBind('my_var', 'Test Value', 
'20150213-231621_168813393')
     angularBind: function(varName, value, paragraphId) {
@@ -36,6 +51,7 @@ angular.module('zeppelinWebApp')
         websocketMsgSrv.clientBindAngularObject($routeParams.noteId, varName, 
value, paragraphId);
       }
     },
+
     // Example: z.angularUnBind('my_var', '20150213-231621_168813393')
     angularUnbind: function(varName, paragraphId) {
       // Only push to server if paragraphId is defined
@@ -55,8 +71,9 @@ angular.module('zeppelinWebApp')
   };
 
   // Controller init
-  $scope.init = function(newParagraph) {
+  $scope.init = function(newParagraph, note) {
     $scope.paragraph = newParagraph;
+    $scope.parentNote = note;
     $scope.originalText = angular.copy(newParagraph.text);
     $scope.chart = {};
     $scope.colWidthOption = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];

Reply via email to