GitHub user doanduyhai opened a pull request:

    https://github.com/apache/incubator-zeppelin/pull/678

    [Zeppelin-] AngularJS utilities functions and Hide/Show ParagraphId feature

    ### What is this PR for?
    This is a PR to add **utilities functions for the AngularJS system** and a 
new **Hide/Show Paragraph Id** feature. 
    
    > See screenshots below to have a feeling of how it works
    
    #### 1) AngularJS Utility Functions
    
    * **runParagraph('paragraphId')** : run a paragraph given its id
    * **runParagraphs(['paragraphId1', 'paragraphId2'])** : run a list of 
paragraphs given their id
    * **pushToServer('varName', value, parameters)** : push a variable 
_varName_ with its _value_ to the server/interpreter **angular object 
repository** using current **noteId** and provided interpreter(s) (and 
optionally paragraph id(s)). _parameters_ is a map with the following 
properties:
    
        Ex: 
    
        ```javascript
        parameters = {
             interpreter: 'spark',
             interpreters: ['md', 'sh'],
             paragraph: '20160126-153136_1060166247',
             paragraphs: ['20160126-171914_843040190', 
'20160126-181556_1915782845'],
             scope: 'note',
             runParagraphs: false    
        }
        ```
    
        * **interpreter**: name of the interpreter to be used for angular 
object repository lookup
        * **interpreters**: name of the interpreters to be used for angular 
object repository lookup
        * **paragraph**: paragraph id used to update the angular object 
repository
        * **paragraphs**: paragraph ids used to update the angular object 
repository
        * **scope**: scope of the angular object, can be _note_ or _paragraph_. 
Default = _paragraph_
        * **runParagraphs**: if _true_, run the paragraphs listed by the 
properties **paragraph** or **paragraphs**. Default = _true_
     <br/>
    
    Remarks:  
    >    1. if both **interpreter** and **interpreters** are provided, the 
single **interpreter** will be added to    the **interpreters** list. Same 
remark for **paragraph** and **paragraphs**
    >    2. if a list of **interpreters** and **paragraphs** are provided, 
**for each interpreter the object will be pushed only to the paragraphs which 
matches this interpreter**
    >    3. if one or several **paragraph** is provided, the object will be 
pushed to the registry using the _(current note id, paragraph id)_ scope. This 
correspond to the default _paragraph_ value for the **scope** variable. 
However, it is also possible to save the same object to the _current note id_ 
scope. In this case set the **scope** variable to _note_. In this case the 
object will be saved using paragraph scope and note scope
    >    4. if **scope** = _paragraph_ and no paragraph id is provided, the 
object will be saved using the _current note id_ scope
    >    5. if **runParagraphs** = _true_ and no paragraph id is provided, it 
will be ignored
    >    6. set **runParagraphs** to _false_ if you want to push the variable 
to the back-end without refreshing paragraph
    
    * **removeFromServer('varName', parameters)**: remove the variable 
_varName_  from server/interpreter **angular object repository** using current 
**noteId**.
    
        Ex: 
    
        ```javascript
        parameters = {
             interpreter: 'spark',
             interpreters: ['md', 'sh'],
             paragraph: '20160126-153136_1060166247',
             paragraphs: ['20160126-171914_843040190', 
'20160126-181556_1915782845']
        }
        ```
        * **interpreter**: name of the interpreter to be used for angular 
object repository lookup
        * **interpreters**: name of the interpreters to be used for angular 
object repository lookup
        * **paragraph**: paragraph id used to remove object from the angular 
object repository
        * **paragraphs**: paragraph ids used to remove object from the angular 
object repository
    
    Remarks:  
    >    1. if both **interpreter** and **interpreters** are provided, the 
single **interpreter** will be added to    the **interpreters** list. Same 
remark for **paragraph** and **paragraphs**
    >    2. if a list of **interpreters** and **paragraphs** are provided, 
**for each interpreter the object will be pushed only to the paragraphs which 
matches this interpreter**
    >    3. if one or several **paragraph** is provided, the object will be 
remove from the registry using the _(current note id, paragraph id)_ scope
    >    4. if no **paragraph** is provided, the object will be remove from the 
registry using the _current note id_ scope
    
    #### 2) Hide/Show Paragraph Id
    
     This feature is necessary for the above AngularJS functions to work. One 
can always retrieve the current paragraph Id using the code
    
    ```scala
     z.getInterpreterContext().getParagraphId()
    ```
    
    But it is far from being user-friendly. Therefore, now you can show or hide 
paragraph Id using a toggle. This is placed at the same place as Show/Hide line 
numbers.
    
    The paragraph id is displayed at the top left corner, above the Title. I'm 
completely open to any suggestion with regard to the location of the display
    
    #### 3) Technical Impl
    
    1. First I have added a new **Thrift** method _angularRegistryPush()_. This 
method is used to push the current angular object registry from the Zeppelin 
server to the remote interpreter. According to @leemoonsoo, when a note is 
loaded from the JSON file, the interpreter **may not be initialized** yet so we 
need to store all the angular variables in the local registry. As soon as the 
remote interpreter is started, we can call the _angularRegistryPush()_ method 
to push the local registry to the remote interpreter
    
    2. A new public constructor has been added  to the `AngularObject` class. 
The reason is because when we push the whole `Map<String, Map<String, 
AngularObject>>` registry to the remote interpreter using 
_angularRegistryPush()_, the registry is serialized using **Gson**. At the 
remote interpreter side, the payload is deserialized back into a `Map<String, 
Map<String, AngularObject>>`. If a class **do not have a public constructor** 
then **Gson** will use some `Unsafe` tricks to create it (see **[here]**). 
Consequently, the `private transient List<AngularObjectWatcher> watchers = new 
LinkedList<AngularObjectWatcher>();` property will NOT be initialized and will 
throw `NullPointerException` later when the code access the `watchers`
    
    3. The `runParagraph()` and `runParagraphs()` Angular functions are 
injected into the **isolated scope** create for each paragraph so that people 
can access them. Currently I'm using the following trick to retrieve the 
paragraph details for a given paragraphId:
    
        ```javascript
        var paragraphDiv = angular.element('#' + paragraphId +
                                        
'_paragraphColumn_main[ng-controller="ParagraphCtrl"]');
        
        var paragraph = paragraphDiv.scope().paragraph;
        ```
         It's not the cleanest way. Ideally, we should inject into the isolated 
scope the `note.paragraphs` array from the Note scope but I'm not sure if it is 
recommended to leak such information into an isolated scope. Any 
idea/suggestion is welcomed
    
    4. New message operations **`ANGULAR_OBJECT_CLIENT_UPDATE`**  and 
**`ANGULAR_OBJECT_CLIENT_REMOVE`** have been added to support `pushToServer()` 
and `removeFromServer()` Angular functions. The core functionalities are 
implemented in `NotebookServer.angularObjectClientUpdate()` and 
`NotebookServer.angularObjectClientDelete()`. I also add 8 unit tests for those 
2 methods.
      
        > A remark on the unit tests. I had to mock a bunch of classes for the 
tests, which suggest that the code is somehow entangled. I try to make the 
method as modular as possible but there are a lot of dependencies on objects 
like Notebook, Note, InterpreterGroup etc ... Maybe a general refactoring later 
would be nice
    
    5. The `ZeppelinContext.getAngularObject()` private method has been 
updated. Right now it look for variable using only the note scope. I change the 
code so that now, we look for a variable using:
        * first _(noteId, current paragraphId)_ as key
        * if no object found, then use _noteId_ as key
        * if no object found, then look at the **global scope** 
    
    6. Last but not least, **I have changed the current behavior of dynamic 
form**. In the method `Paragraph.jobRun()`, when we encounter a variable 
definition block ( ${var = ...}) while parsing, we first look into the 
**angular object registry** using _(noteId, paragraphId)_ scope, then 
_(noteId)_ scope. If we find a matching variable we'll use it and **we do not 
create any form input for the paragraph**. At worst, we can just remove the 
last commit, it is independent from the other features.
    
    @Leemoonsoo  @jongyoul @bzz @felixcheung @corneadoug  I really need your 
remarks/comments for the above 6 technical points. Thanks
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    * [ ] - Code review by committers/contributors/community
    * [ ] - Responses and suggestions for the technical questions raised in 
chapter **3) Technical Impl**
    * [ ] - Features tested following the below step
    
    
    ### Is there a relevant Jira issue?
    **[ZEPPELIN-635]**
    
    ### How should this be tested?
    
    **See below 12 screenshots for all possible test scenarios**
    
    ### Screenshots (if appropriate)
    
    **Hide/Show ParagraphId**
    ![paragraphid 
demo](https://cloud.githubusercontent.com/assets/1532977/12610908/8188294a-c4ea-11e5-95f3-c249aaad54bc.gif)
    
    **Run Single Paragraph**
    ![runparagraph - scenario 
1](https://cloud.githubusercontent.com/assets/1532977/12610929/9c227bb6-c4ea-11e5-9dc7-b30cc1edf848.gif)
    
    **Run Multiple Paragraphs**
    ![runparagraph - scenario 
2](https://cloud.githubusercontent.com/assets/1532977/12610930/a35e4982-c4ea-11e5-800e-b879a9cd0ef9.gif)
    
    **Push To Server Scenario 1**: single interpreter, **note** scope
    ![pushtoserver - scenario 
1](https://cloud.githubusercontent.com/assets/1532977/12610925/9342db8a-c4ea-11e5-850f-e3f65fb55d63.gif)
    
    **Push To Server Scenario 2**: multiple interpreters, **note** scope
    ![pushtoserver - scenario 
2](https://cloud.githubusercontent.com/assets/1532977/12610939/a980e2ac-c4ea-11e5-92bf-3b41d399f7fc.gif)
    
    **Push To Server Scenario 3**: single interpreter, single paragraph
    ![pushtoserver - scenario 
3](https://cloud.githubusercontent.com/assets/1532977/12610942/afe3ee00-c4ea-11e5-8039-d4502670513b.gif)
    
    **Push To Server Scenario 4**: single interpreter, multiples paragraphs
    ![pushtoserver - scenario 
4](https://cloud.githubusercontent.com/assets/1532977/12610947/b5959466-c4ea-11e5-9b4c-84cc9c346e26.gif)
    
    **Push To Server Scenario 5**: single interpreter, multiples paragraphs AND 
note scope
    ![pushtoserver - scenario 
5](https://cloud.githubusercontent.com/assets/1532977/12610953/bc3817da-c4ea-11e5-89ae-6171acb2cfee.gif)
    
    **Push To Server Scenario 6**: single interpreter, multiples paragraphs AND 
run paragraphs
    ![pushtoserver - scenario 
6](https://cloud.githubusercontent.com/assets/1532977/12610959/c26ad93a-c4ea-11e5-9ce6-92d07518efd3.gif)
    
    **Push To Server Scenario 7**: multiple interpreters, multiples paragraphs, 
**overriding dynamic forms**
    ![pushtoserver - scenario 
7](https://cloud.githubusercontent.com/assets/1532977/12610962/c874acde-c4ea-11e5-8558-7c24b1ae14e1.gif)
    
    **Remove From Server Scenario 1**: single interpreter, single paragraph
    ![removefromserver - scenario 
1](https://cloud.githubusercontent.com/assets/1532977/12610973/ce273390-c4ea-11e5-870c-03415b9093fc.gif)
    
    **Remove From Server Scenario 2**: single interpreter, **note** scope
    ![removefromserver - scenario 
2](https://cloud.githubusercontent.com/assets/1532977/12610985/d560dc2e-c4ea-11e5-9297-9b997d1a7087.gif)
    
    ### Questions:
    * Does the licenses files need update? --> **No**
    * Is there breaking changes for older versions? --> **No**
    * Does this needs documentation? --> **Yes**
    
    [here]: 
http://stackoverflow.com/questions/18645050/is-default-no-args-constructor-mandatory-for-gson
    [ZEPPELIN-635]: https://issues.apache.org/jira/browse/ZEPPELIN-635

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/doanduyhai/incubator-zeppelin 
PushToServer_Angular_Function

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/incubator-zeppelin/pull/678.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #678
    
----
commit 7c1e284b0509e9cd8419fe83485643d1a7ac7e66
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-24T18:55:30Z

    Add Thrift RPC method angularRegistryPush()

commit ecb3a219478cc257d5069faccb00227c497569a0
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-26T15:50:10Z

    Make AngularObject constructor public because of serialization issue

commit 65985cab69f337c1e31c7d667defd79b94b334ed
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-26T19:30:36Z

    Implement runParagraph() and runParagraphs() Angular functions

commit 6eeaa4bd02ca78df9259342f9917c4f62b024c37
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-26T12:41:12Z

    Implement pushToServer() Angular function

commit 96c30798b80a43ff6518c39138cb7a19361f5843
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-26T15:51:11Z

    ZeppelinContext angular() method should look for variable using the 
paragraph scope, then note scope and then global scope, in this order

commit 21a12ddd2d3ca2b81ef20e5fa3db6a3ab4793227
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-24T20:31:53Z

    Add Hide/Show paragraph id feature

commit b544b0c47bd3af05e7a85a1d9d12b260e142988e
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-25T20:13:53Z

    Replace dynamic form with angular object from registry if exists

commit bf4b29317c6e0aa09e48a47cb8a0849016362fa8
Author: DuyHai DOAN <doanduy...@gmail.com>
Date:   2016-01-26T19:29:20Z

    Implement removeFromServer() Angular function

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastruct...@apache.org or file a JIRA ticket
with INFRA.
---

Reply via email to