Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master 0629d9370 -> 34a52edd6


[ZEPPELIN-693] Add AngularJS z.angularUnbind()

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

The signature of the method is `angularUnbind(varName, paragraphId)`.

_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-693]**

### How should this be tested?
* `git fetch origin pull/741/head:AngularJSUnbind`
* `git checkout AngularJSUnbind`
* `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="superheroId">Super Hero: </label>
    <input type="text" class="form-control" id="superheroId" 
placeholder="Superhero name ..." ng-model="superhero"></input>
  </div>
  <button type="submit" class="btn btn-primary" 
ng-click="z.angularBind('superhero', superhero, PUT_HERE_PARAGRAPH_ID')"> 
Angular Bind</button>
  <button type="submit" class="btn btn-primary" 
ng-click="z.angularUnbind('superhero', 'PUT_HERE_PARAGRAPH_ID')"> Angular 
UnBind</button>
</form>
```
* Create a second paragraph with the following code:

```scala
z.angular("superhero")
```

* Retrieve the paragraph id of the second paragraph
* In the first paragraph, replace the text PUT_HERE_PARAGRAPH_ID by the correct 
paragraph id
* In the input text, put "Superman" and click on the **Bind Angular** button
* Execute the second paragraph to see that the superhero variable is now set to 
Superman
* Now click on the **Unbind Angular** button from the first paragraph
* Refresh the second paragraph to check that the _superhero_ variable has been 
removed

### Screenshots (if appropriate)
![angularunbind](https://cloud.githubusercontent.com/assets/1532977/13891568/30010c32-ed52-11e5-86c4-6c81efc35d27.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-693]: https://issues.apache.org/jira/browse/ZEPPELIN-693

Author: DuyHai DOAN <[email protected]>

Closes #741 from doanduyhai/ZEPPELIN-693 and squashes the following commits:

3f01b04 [DuyHai DOAN] [ZEPPELIN-693] Add AngularJS z.angularUnbind()


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

Branch: refs/heads/master
Commit: 34a52edd6c4ec95869f9e37af9f92f11e1ab2b99
Parents: 0629d93
Author: DuyHai DOAN <[email protected]>
Authored: Thu Mar 17 15:50:29 2016 +0100
Committer: Lee moon soo <[email protected]>
Committed: Tue Mar 22 10:02:12 2016 -0700

----------------------------------------------------------------------
 .../org/apache/zeppelin/socket/Message.java     |  2 +
 .../apache/zeppelin/socket/NotebookServer.java  | 70 +++++++++++++++
 .../zeppelin/socket/NotebookServerTest.java     | 90 ++++++++++++++++++++
 .../notebook/paragraph/paragraph.controller.js  |  7 ++
 .../websocketEvents/websocketMsg.service.js     | 11 +++
 5 files changed, 180 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/34a52edd/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
index f091364..913e184 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
@@ -105,6 +105,8 @@ public class Message {
 
     ANGULAR_OBJECT_CLIENT_BIND,  // [c-s] angular object updated from 
AngularJS z object
 
+    ANGULAR_OBJECT_CLIENT_UNBIND,  // [c-s] angular object unbind from 
AngularJS z object
+
     LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations
     CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations
                   // @param settings serialized Map<String, String> object

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/34a52edd/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 98a1aaa..9412d71 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -190,6 +190,9 @@ public class NotebookServer extends WebSocketServlet 
implements
           case ANGULAR_OBJECT_CLIENT_BIND:
             angularObjectClientBind(conn, userAndRoles, notebook, 
messagereceived);
             break;
+          case ANGULAR_OBJECT_CLIENT_UNBIND:
+            angularObjectClientUnbind(conn, userAndRoles, notebook, 
messagereceived);
+            break;
           case LIST_CONFIGURATIONS:
             sendAllConfigurations(conn, userAndRoles, notebook);
             break;
@@ -769,6 +772,45 @@ public class NotebookServer extends WebSocketServlet 
implements
     }
   }
 
+  /**
+   * Remove the given Angular variable to the target
+   * interpreter(s) angular registry given a noteId
+   * and an optional list of paragraph id(s)
+   * @param conn
+   * @param notebook
+   * @param fromMessage
+   * @throws Exception
+   */
+  protected void angularObjectClientUnbind(NotebookSocket conn, 
HashSet<String> userAndRoles,
+                                           Notebook notebook, Message 
fromMessage)
+      throws Exception{
+    String noteId = fromMessage.getType("noteId");
+    String varName = fromMessage.getType("name");
+    String paragraphId = fromMessage.getType("paragraphId");
+    Note note = notebook.getNote(noteId);
+
+    if (paragraphId == null) {
+      throw new IllegalArgumentException("target paragraph not specified for " 
+
+              "angular value unBind");
+    }
+
+    if (note != null) {
+      final InterpreterGroup interpreterGroup = 
findInterpreterGroupForParagraph(note,
+              paragraphId);
+
+      final AngularObjectRegistry registry = 
interpreterGroup.getAngularObjectRegistry();
+
+      if (registry instanceof RemoteAngularObjectRegistry) {
+        RemoteAngularObjectRegistry remoteRegistry = 
(RemoteAngularObjectRegistry) registry;
+        removeAngularFromRemoteRegistry(noteId, paragraphId, varName, 
remoteRegistry,
+                interpreterGroup.getId(), conn);
+      } else {
+        removeAngularObjectFromLocalRepo(noteId, paragraphId, varName, 
registry,
+                interpreterGroup.getId(), conn);
+      }
+    }
+  }
+
   private InterpreterGroup findInterpreterGroupForParagraph(Note note, String 
paragraphId)
       throws Exception {
     final Paragraph paragraph = note.getParagraph(paragraphId);
@@ -794,6 +836,20 @@ public class NotebookServer extends WebSocketServlet 
implements
             conn);
   }
 
+  private void removeAngularFromRemoteRegistry(String noteId, String 
paragraphId,
+    String varName, RemoteAngularObjectRegistry remoteRegistry,
+    String interpreterGroupId, NotebookSocket conn) {
+    final AngularObject ao = 
remoteRegistry.removeAndNotifyRemoteProcess(varName, noteId,
+            paragraphId);
+    this.broadcastExcept(
+            noteId,
+            new Message(OP.ANGULAR_OBJECT_REMOVE).put("angularObject", ao)
+                    .put("interpreterGroupId", interpreterGroupId)
+                    .put("noteId", noteId)
+                    .put("paragraphId", paragraphId),
+            conn);
+  }
+
   private void pushAngularObjectToLocalRepo(String noteId, String paragraphId, 
String varName,
     Object varValue, AngularObjectRegistry registry,
     String interpreterGroupId, NotebookSocket conn) {
@@ -813,6 +869,20 @@ public class NotebookServer extends WebSocketServlet 
implements
             conn);
   }
 
+  private void removeAngularObjectFromLocalRepo(String noteId, String 
paragraphId, String varName,
+    AngularObjectRegistry registry, String interpreterGroupId, NotebookSocket 
conn) {
+    final AngularObject removed = registry.remove(varName, noteId, 
paragraphId);
+    if (removed != null) {
+      this.broadcastExcept(
+              noteId,
+              new Message(OP.ANGULAR_OBJECT_REMOVE).put("angularObject", 
removed)
+                      .put("interpreterGroupId", interpreterGroupId)
+                      .put("noteId", noteId)
+                      .put("paragraphId", paragraphId),
+              conn);
+    }
+  }
+
   private void moveParagraph(NotebookSocket conn, HashSet<String> 
userAndRoles, Notebook notebook,
       Message fromMessage) throws IOException {
     final String paragraphId = (String) fromMessage.get("id");

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/34a52edd/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
index 6989c16..98895de 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
@@ -262,6 +262,96 @@ public class NotebookServerTest extends 
AbstractTestRestApi {
     verify(otherConn).send(mdMsg1);
   }
 
+  @Test
+  public void should_unbind_angular_object_from_remote_for_paragraphs() throws 
Exception {
+    //Given
+    final String varName = "name";
+    final String value = "val";
+    final Message messageReceived = new 
Message(OP.ANGULAR_OBJECT_CLIENT_UNBIND)
+            .put("noteId", "noteId")
+            .put("name", varName)
+            .put("paragraphId", "paragraphId");
+
+    final NotebookServer server = new NotebookServer();
+    final Notebook notebook = mock(Notebook.class);
+    final Note note = mock(Note.class, RETURNS_DEEP_STUBS);
+    when(notebook.getNote("noteId")).thenReturn(note);
+    final Paragraph paragraph = mock(Paragraph.class, RETURNS_DEEP_STUBS);
+    when(note.getParagraph("paragraphId")).thenReturn(paragraph);
+
+    final RemoteAngularObjectRegistry mdRegistry = 
mock(RemoteAngularObjectRegistry.class);
+    final InterpreterGroup mdGroup = new InterpreterGroup("mdGroup");
+    mdGroup.setAngularObjectRegistry(mdRegistry);
+
+    when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup);
+
+    final AngularObject ao1 = AngularObjectBuilder.build(varName, value, 
"noteId", "paragraphId");
+    when(mdRegistry.removeAndNotifyRemoteProcess(varName, "noteId", 
"paragraphId")).thenReturn(ao1);
+    NotebookSocket conn = mock(NotebookSocket.class);
+    NotebookSocket otherConn = mock(NotebookSocket.class);
+
+    final String mdMsg1 =  server.serializeMessage(new 
Message(OP.ANGULAR_OBJECT_REMOVE)
+            .put("angularObject", ao1)
+            .put("interpreterGroupId", "mdGroup")
+            .put("noteId", "noteId")
+            .put("paragraphId", "paragraphId"));
+
+    server.noteSocketMap.put("noteId", asList(conn, otherConn));
+
+    // When
+    server.angularObjectClientUnbind(conn, new HashSet<String>(), notebook, 
messageReceived);
+
+    // Then
+    verify(mdRegistry, never()).removeAndNotifyRemoteProcess(varName, 
"noteId", null);
+
+    verify(otherConn).send(mdMsg1);
+  }
+
+  @Test
+  public void should_unbind_angular_object_from_local_for_paragraphs() throws 
Exception {
+    //Given
+    final String varName = "name";
+    final String value = "val";
+    final Message messageReceived = new 
Message(OP.ANGULAR_OBJECT_CLIENT_UNBIND)
+            .put("noteId", "noteId")
+            .put("name", varName)
+            .put("paragraphId", "paragraphId");
+
+    final NotebookServer server = new NotebookServer();
+    final Notebook notebook = mock(Notebook.class);
+    final Note note = mock(Note.class, RETURNS_DEEP_STUBS);
+    when(notebook.getNote("noteId")).thenReturn(note);
+    final Paragraph paragraph = mock(Paragraph.class, RETURNS_DEEP_STUBS);
+    when(note.getParagraph("paragraphId")).thenReturn(paragraph);
+
+    final AngularObjectRegistry mdRegistry = mock(AngularObjectRegistry.class);
+    final InterpreterGroup mdGroup = new InterpreterGroup("mdGroup");
+    mdGroup.setAngularObjectRegistry(mdRegistry);
+
+    when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup);
+
+    final AngularObject ao1 = AngularObjectBuilder.build(varName, value, 
"noteId", "paragraphId");
+
+
+    when(mdRegistry.remove(varName, "noteId", "paragraphId")).thenReturn(ao1);
+
+    NotebookSocket conn = mock(NotebookSocket.class);
+    NotebookSocket otherConn = mock(NotebookSocket.class);
+
+    final String mdMsg1 =  server.serializeMessage(new 
Message(OP.ANGULAR_OBJECT_REMOVE)
+            .put("angularObject", ao1)
+            .put("interpreterGroupId", "mdGroup")
+            .put("noteId", "noteId")
+            .put("paragraphId", "paragraphId"));
+    server.noteSocketMap.put("noteId", asList(conn, otherConn));
+
+    // When
+    server.angularObjectClientUnbind(conn, new HashSet<String>(), notebook, 
messageReceived);
+
+    // Then
+    verify(otherConn).send(mdMsg1);
+  }
+
   private NotebookSocket createWebSocket() {
     NotebookSocket sock = mock(NotebookSocket.class);
     when(sock.getRequest()).thenReturn(createHttpServletRequest());

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/34a52edd/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 097ee84..a6489fd 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -35,6 +35,13 @@ angular.module('zeppelinWebApp')
       if (paragraphId) {
         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
+      if (paragraphId) {
+        websocketMsgSrv.clientUnbindAngularObject($routeParams.noteId, 
varName, paragraphId);
+      }
     }
   };
 

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/34a52edd/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js 
b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
index 3fba0f5..3b4df03 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -82,6 +82,17 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', 
function($rootScope,
       });
     },
 
+    clientUnbindAngularObject: function(noteId, name, paragraphId) {
+      websocketEvents.sendNewEvent({
+        op: 'ANGULAR_OBJECT_CLIENT_UNBIND',
+        data: {
+          noteId: noteId,
+          name: name,
+          paragraphId: paragraphId
+        }
+      });
+    },
+
     cancelParagraphRun: function(paragraphId) {
       websocketEvents.sendNewEvent({op: 'CANCEL_PARAGRAPH', data: {id: 
paragraphId}});
     },

Reply via email to