Merge remote-tracking branch 'origin/master' into livyInterperter

This commit is contained in:
Prabhjyot Singh 2016-05-10 10:49:34 +05:30
commit 134923ddfc
38 changed files with 543 additions and 74 deletions

View file

@ -81,11 +81,17 @@ before_script:
script:
- mvn $TEST_FLAG $PROFILE -B $TEST_PROJECTS
after_success:
- echo "Travis exited with ${TRAVIS_TEST_RESULT}"
after_failure:
- echo "Travis exited with ${TRAVIS_TEST_RESULT}"
- cat target/rat.txt
- cat zeppelin-server/target/rat.txt
- cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.log
- cat zeppelin-distribution/target/zeppelin-*-SNAPSHOT/zeppelin-*-SNAPSHOT/logs/zeppelin*.out
- cat zeppelin-web/npm-debug.log
after_script:
- ./testing/stopSparkCluster.sh $SPARK_VER $HADOOP_VER

106
README.md
View file

@ -1,4 +1,4 @@
#Zeppelin
#Zeppelin
**Documentation:** [User Guide](http://zeppelin.incubator.apache.org/docs/latest/index.html)<br/>
**Mailing Lists:** [User and Dev mailing list](http://zeppelin.incubator.apache.org/community.html)<br/>
@ -21,15 +21,15 @@ To know more about Zeppelin, visit our web site [http://zeppelin.incubator.apach
* Java 1.7
* Tested on Mac OSX, Ubuntu 14.X, CentOS 6.X
* Maven (if you want to build from the source code)
* Node.js Package Manager
* Node.js Package Manager (npm)
## Getting Started
### Before Build
If you don't have requirements prepared, install it.
If you don't have requirements prepared, install it.
(The installation method may vary according to your environment, example is for Ubuntu.)
```
```sh
sudo apt-get update
sudo apt-get install git
sudo apt-get install openjdk-7-jdk
@ -42,10 +42,10 @@ sudo tar -zxf apache-maven-3.3.3-bin.tar.gz -C /usr/local/
sudo ln -s /usr/local/apache-maven-3.3.3/bin/mvn /usr/local/bin/mvn
```
_Notes:_
_Notes:_
- Ensure node is installed by running `node --version`
- Ensure maven is running version 3.1.x or higher with `mvn -version`
- Configure maven to use more memory than usual by ```export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=1024m"```
- Configure maven to use more memory than usual by `export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=1024m"`
### Build
If you want to build Zeppelin from the source, please first clone this repository, then:
@ -61,7 +61,7 @@ Each Interpreter requires different Options.
To build with a specific Spark version, Hadoop version or specific features, define one or more of the following profiles and options:
##### -Pspark-[version]
##### `-Pspark-[version]`
Set spark major version
@ -84,7 +84,7 @@ Available profiles are
minor version can be adjusted by `-Dspark.version=x.x.x`
##### -Phadoop-[version]
##### `-Phadoop-[version]`
set hadoop major version
@ -101,25 +101,32 @@ Available profiles are
minor version can be adjusted by `-Dhadoop.version=x.x.x`
##### -Pyarn (optional)
##### `-Pyarn` (optional)
enable YARN support for local mode
> YARN for local mode is not supported for Spark v1.5.0 or higher. Set SPARK_HOME instead.
> YARN for local mode is not supported for Spark v1.5.0 or higher. Set `SPARK_HOME` instead.
##### -Ppyspark (optional)
##### `-Ppyspark` (optional)
enable PySpark support for local mode
enable [PySpark](http://spark.apache.org/docs/latest/api/python/) support for local mode.
##### `-Pr` (optional)
##### -Pvendor-repo (optional)
enable [R](https://www.r-project.org/) support with [SparkR](https://spark.apache.org/docs/latest/sparkr.html) integration.
##### `-Psparkr` (optional)
another [R](https://www.r-project.org/) support with [SparkR](https://spark.apache.org/docs/latest/sparkr.html) integration as well as local mode support.
##### `-Pvendor-repo` (optional)
enable 3rd party vendor repository (cloudera)
##### -Pmapr[version] (optional)
##### `-Pmapr[version]` (optional)
For the MapR Hadoop Distribution, these profiles will handle the Hadoop version. As MapR allows different versions
of Spark to be installed, you should specify which version of Spark is installed on the cluster by adding a Spark profile (-Pspark-1.2, -Pspark-1.3, etc.) as needed. For Hive, check the hive/pom.xml and adjust the version installed as well. The correct Maven
For the MapR Hadoop Distribution, these profiles will handle the Hadoop version. As MapR allows different versions of Spark to be installed, you should specify which version of Spark is installed on the cluster by adding a Spark profile (`-Pspark-1.2`, `-Pspark-1.3`, etc.) as needed.
For Hive, check the hive/pom.xml and adjust the version installed as well. The correct Maven
artifacts can be found for every version of MapR at http://doc.mapr.com
Available profiles are
@ -135,7 +142,7 @@ Available profiles are
Here're some examples:
```
```sh
# basic build
mvn clean package -Pspark-1.6 -Phadoop-2.4 -Pyarn -Ppyspark
@ -152,13 +159,13 @@ mvn clean package -Pspark-1.5 -Pmapr50 -DskipTests
#### Ignite Interpreter
```
```sh
mvn clean package -Dignite.version=1.1.0-incubating -DskipTests
```
#### Scalding Interpreter
```
```sh
mvn clean package -Pscalding -DskipTests
```
@ -169,67 +176,80 @@ If you wish to configure Zeppelin option (like port number), configure the follo
./conf/zeppelin-env.sh
./conf/zeppelin-site.xml
```
(You can copy ```./conf/zeppelin-env.sh.template``` into ```./conf/zeppelin-env.sh```.
Same for ```zeppelin-site.xml```.)
(You can copy `./conf/zeppelin-env.sh.template` into `./conf/zeppelin-env.sh`.
Same for `zeppelin-site.xml`.)
#### Setting SPARK_HOME and HADOOP_HOME
Without SPARK_HOME and HADOOP_HOME, Zeppelin uses embedded Spark and Hadoop binaries that you have specified with mvn build option.
If you want to use system provided Spark and Hadoop, export SPARK_HOME and HADOOP_HOME in zeppelin-env.sh
Without `SPARK_HOME` and `HADOOP_HOME`, Zeppelin uses embedded Spark and Hadoop binaries that you have specified with mvn build option.
If you want to use system provided Spark and Hadoop, export `SPARK_HOME` and `HADOOP_HOME` in `zeppelin-env.sh`.
You can use any supported version of spark without rebuilding Zeppelin.
```
```sh
# ./conf/zeppelin-env.sh
export SPARK_HOME=...
export HADOOP_HOME=...
```
#### External cluster configuration
Mesos
# ./conf/zeppelin-env.sh
export MASTER=mesos://...
export ZEPPELIN_JAVA_OPTS="-Dspark.executor.uri=/path/to/spark-*.tgz" or SPARK_HOME="/path/to/spark_home"
export MESOS_NATIVE_LIBRARY=/path/to/libmesos.so
```sh
# ./conf/zeppelin-env.sh
export MASTER=mesos://...
export ZEPPELIN_JAVA_OPTS="-Dspark.executor.uri=/path/to/spark-*.tgz" or SPARK_HOME="/path/to/spark_home"
export MESOS_NATIVE_LIBRARY=/path/to/libmesos.so
```
If you set `SPARK_HOME`, you should deploy spark binary on the same location to all worker nodes. And if you set `spark.executor.uri`, every worker can read that file on its node.
Yarn
# ./conf/zeppelin-env.sh
export SPARK_HOME=/path/to/spark_dir
```sh
# ./conf/zeppelin-env.sh
export SPARK_HOME=/path/to/spark_dir
```
### Run
./bin/zeppelin-daemon.sh start
browse localhost:8080 in your browser.
```sh
./bin/zeppelin-daemon.sh start
```
And browse [localhost:8080](localhost:8080) in your browser.
For configuration details check __./conf__ subdirectory.
For configuration details check __`./conf`__ subdirectory.
### Package
To package the final distribution including the compressed archive, run:
mvn clean package -Pbuild-distr
```sh
mvn clean package -Pbuild-distr
```
To build a distribution with specific profiles, run:
mvn clean package -Pbuild-distr -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
```sh
mvn clean package -Pbuild-distr -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
```
The profiles `-Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark` can be adjusted if you wish to build to a specific spark versions, or omit support such as `yarn`.
The archive is generated under _zeppelin-distribution/target_ directory
The archive is generated under _`zeppelin-distribution/target`_ directory
###Run end-to-end tests
Zeppelin comes with a set of end-to-end acceptance tests driving headless selenium browser
#assumes zeppelin-server running on localhost:8080 (use -Durl=.. to override)
mvn verify
#or take care of starting\stoping zeppelin-server from packaged _zeppelin-distribuion/target_
mvn verify -P using-packaged-distr
```sh
# assumes zeppelin-server running on localhost:8080 (use -Durl=.. to override)
mvn verify
# or take care of starting/stoping zeppelin-server from packaged zeppelin-distribuion/target
mvn verify -P using-packaged-distr
```
[![Analytics](https://ga-beacon.appspot.com/UA-45176241-4/apache/incubator-zeppelin/README.md?pixel)](https://github.com/igrigorik/ga-beacon)

View file

@ -29,6 +29,10 @@ user3 = password4, role2
#ldapRealm.userDnTemplate = cn={0},cn=engg,ou=testdomain,dc=testdomain,dc=com
#ldapRealm.contextFactory.url = ldap://ldaphost:389
#ldapRealm.contextFactory.authenticationMechanism = SIMPLE
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
# 86,400,000 milliseconds = 24 hour
securityManager.sessionManager.globalSessionTimeout = 86400000
shiro.loginUrl = /api/login
[urls]

View file

@ -31,6 +31,8 @@
<!-- li><span><b>Tutorial</b><span></li -->
<li><a href="{{BASE_PATH}}/tutorial/tutorial.html">Tutorial</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{BASE_PATH}}/ui_layout/zeppelin_layout.html">UI Layout</a></li>
<li role="separator" class="divider"></li>
<!-- li><span><b>Guide</b><span></li -->
<li><a href="{{BASE_PATH}}/manual/dynamicform.html">Dynamic Form</a></li>
<li><a href="{{BASE_PATH}}/manual/publish.html">Publish your Paragraph</a></li>
@ -71,7 +73,8 @@
<li><a href="{{BASE_PATH}}/displaysystem/display.html">Text</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/display.html#html">Html</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/table.html">Table</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/angular.html">Angular</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/back-end-angular.html">Angular (backend API)</a></li>
<li><a href="{{BASE_PATH}}/displaysystem/front-end-angular.html">Angular (frontend API)</a></li>
</ul>
</li>
<li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

View file

@ -1,7 +1,7 @@
---
layout: page
title: "Angular Display System"
description: ""
title: "Angular (backend API)"
description: "Angular (backend API)"
group: display
---
<!--
@ -20,7 +20,7 @@ limitations under the License.
{% include JB/setup %}
## Angular Display System in Zeppelin
## Back-end Angular API in Zeppelin
Angular display system treats output as a view template for [AngularJS](https://angularjs.org/).
It compiles templates and displays them inside of Zeppelin.

View file

@ -0,0 +1,159 @@
---
layout: page
title: "Angular (frontend API)"
description: "Angular (frontend API)"
group: display
---
<!--
Licensed 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.
-->
{% include JB/setup %}
## Front-end Angular API in Zeppelin
In addition to the back-end API to handle Angular objects binding, Zeppelin also exposes a simple AngularJS **`z`** object on the front-end side to expose the same capabilities.
This **`z`** object is accessible in the Angular isolated scope for each paragraph.
<br />
### Bind / Unbind Variables
Through the **`z`**, you can bind / unbind variables to **AngularJS view**
Bind a value to an angular object and a **mandatory** target paragraph:
```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,'20160222-232336_1472609686')"> Bind</button>
</form>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_angularBind.gif" />
<hr/>
Unbind/remove a value from angular object and a **mandatory** target paragraph:
```html
%angular
<form class="form-inline">
<button type="submit" class="btn btn-primary" ng-click="z.angularUnbind('superhero','20160222-232336_1472609686')"> UnBind</button>
</form>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_angularUnbind.gif" />
The signature for the **`z.angularBind() / z.angularUnbind()`** functions are:
```javascript
z.angularBind(angularObjectName, angularObjectValue, paragraphId);
z.angularUnbind(angularObjectName, angularObjectValue, paragraphId);
```
All the parameters are mandatory.
<br />
### Run Paragraph
You can also trigger paragraph execution by calling **`z.runParagraph()`** function passing the appropriate paragraphId:
```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>
```
<img src="/assets/themes/zeppelin/img/screenshots/z_runParagraph.gif" />
<br />
### Overriding dynamic form with Angular Object
The front-end Angular Interaction API has been designed to offer richer form capabilities and variable binding. With the existing **Dynamic Form** system you can already create input text, select and checkbox forms but the choice is rather limited and the look & feel cannot be changed.
The idea is to create a custom form using plain HTML/AngularJS code and bind actions on this form to push/remove Angular variables to targeted paragraphs using this new API.
Consequently if you use the **Dynamic Form** syntax in a paragraph and there is a bound Angular object having the same name as the _${formName}_, the Angular object will have higher priority and the **Dynamic Form** will not be displayed. Example:
<img src="/assets/themes/zeppelin/img/screenshots/z_angularJs_overriding_dynamic_form.gif" />
<br />
### Feature matrix comparison
How does the front-end AngularJS API compares to the back-end API ? Below is a comparison matrix for both APIs:
<table>
<thead>
<tr>
<th>Actions</th>
<th>Front-end API</th>
<th>Back-end API</th>
</tr>
</thead>
<tr>
<td>Initiate binding</td>
<td>z.angularbind(var, initialValue, paragraphId)</td>
<td>z.angularBind(var, initialValue)</td>
</tr>
<tr>
<td>Update value</td>
<td>same to ordinary angularjs scope variable, or z.angularbind(var, newValue, paragraphId)</td>
<td>z.angularBind(var, newValue)</td>
</tr>
<tr>
<td>Watching value</td>
<td>same to ordinary angularjs scope variable</td>
<td>z.angularWatch(var, (oldVal, newVal) => ...)</td>
</tr>
<tr>
<td>Destroy binding</td>
<td>z.angularUnbind(var, paragraphId)</td>
<td>z.angularUnbind(var)</td>
</tr>
<tr>
<td>Executing Paragraph</td>
<td>z.runParagraph(paragraphId)</td>
<td>z.run(paragraphId)</td>
</tr>
<tbody>
<tbody>
</table>
Both APIs are pretty similar, except for value watching where it is done naturally by AngularJS internals on the front-end and by user custom watcher functions in the back-end.
There is also a slight difference in term of scope. Front-end API limits the Angular object binding to a paragraph scope whereas back-end API allows you to bind an Angular object at the global or note scope. This restriction has been designed purposely to avoid Angular object leaks and scope pollution.

View file

@ -20,17 +20,22 @@ Spark Interpreter group, which consisted of 4 interpreters.
<tr>
<td>%spark</td>
<td>SparkInterpreter</td>
<td>Creates SparkContext and provides scala environment</td>
<td>Creates a SparkContext and provides a scala environment</td>
</tr>
<tr>
<td>%pyspark</td>
<td>PySparkInterpreter</td>
<td>Provides python environment</td>
<td>Provides a python environment</td>
</tr>
<tr>
<td>%r</td>
<td>SparkRInterpreter</td>
<td>Provides an R environment with SparkR support</td>
</tr>
<tr>
<td>%sql</td>
<td>SparkSQLInterpreter</td>
<td>Provides SQL environment</td>
<td>Provides a SQL environment</td>
</tr>
<tr>
<td>%dep</td>
@ -40,8 +45,8 @@ Spark Interpreter group, which consisted of 4 interpreters.
</table>
## Configuration
Zeppelin provides the below properties for Spark interpreter.
You can also set other Spark properties which are not listed in the table. If so, please refer to [Spark Available Properties](http://spark.apache.org/docs/latest/configuration.html#available-properties).
The Spark interpreter can be configured with properties provided by Zeppelin.
You can also set other Spark properties which are not listed in the table. For a list of additional properties, refer to [Spark Available Properties](http://spark.apache.org/docs/latest/configuration.html#available-properties).
<table class="table-configuration">
<tr>
<th>Property</th>

View file

@ -88,7 +88,7 @@ you need to do is use our %angular support.
<i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
<ul style="list-style-type: none;">
<li ng-repeat="note in home.notes.list track by $index"><i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}}</a>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{noteName(note)}}</a>
</li>
</ul>
</div>

View file

@ -0,0 +1,139 @@
---
layout: page
title: "Zeppelin UI Layout"
description: "Description of Zeppelin UI Layout"
group: ui_layout
---
<!--
Licensed 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.
-->
## Home Page
The first time you connect to Zeppelin, you'll land at the main page similar to the below screen capture
<img src="/assets/themes/zeppelin/img/ui-img/homepage.png" />
On the left of the page are listed all existing notes. Those notes are stored by default in the `$ZEPPELIN_HOME/notebook` folder.
You can filter them by name using the input text form. You can also create an new note, refresh the list of existing notes
(in case you manually copy them into the `$ZEPPELIN_HOME/notebook` folder) and import a note
<img src="/assets/themes/zeppelin/img/ui-img/notes_management.png" />
When clicking on `Import Note` link, a new dialog open. From there you can import your note from local disk or from a remote location
if you provide the URL.
<img src="/assets/themes/zeppelin/img/ui-img/note_import_dialog.png" />
By default, the name of the imported note is the same as the original note but you can override it by providing a new name
<br />
## Menus
### 1. Notebook
The `Notebook` menu proposes almost the same features as the note management section in the home page. From the drop-down menu you can:
1. Open a selected note
2. Filter node by name
3. Create a new note
<img src="/assets/themes/zeppelin/img/ui-img/notebook_menu.png" />
### 2. Interpreter
In this menu you can:
1. Configure existing **interpreter instance**
2. Add/remove **interpreter instances**
<img src="/assets/themes/zeppelin/img/ui-img/interpreter_menu.png" />
### 3. Configuration
This menu displays all the Zeppelin configuration that are set in the config file `$ZEPPELIN_HOME/conf/zeppelin-site.xml`
<img src="/assets/themes/zeppelin/img/ui-img/configuration_menu.png" />
<br />
## Note Layout
Each Zeppelin note is composed of 1 .. N paragraphs. The note can be viewed as a paragraph container.
<img src="/assets/themes/zeppelin/img/ui-img/note_paragraph_layout.png" />
### Paragraph
Each paragraph consists of 2 sections: `code section` where you put your source code and `result section` where you can see the result of the code execution.
<img src="/assets/themes/zeppelin/img/ui-img/paragraph_layout.png" />
On the top-right corner of each paragraph there are some commands to:
* execute the paragraph code
* hide/show `code section`
* hide/show `result section`
* configure the paragraph
To configure the paragraph, just click on the gear icon:
<img src="/assets/themes/zeppelin/img/ui-img/paragraph_configuration_dialog.png" />
From this dialog, you can (in descending order):
* find the **paragraph id** ( **20150924-163507_134879501** )
* control paragraph width. Since Zeppelin is using the grid system of **Twitter Bootstrap**, each paragraph width can be changed from 1 to 12
* move the paragraph 1 level up
* move the paragraph 1 level down
* create a new paragraph
* change paragraph title
* show/hide line number in the `code section`
* disable the run button for this paragraph
* export the current paragraph as an **iframe** and open the **iframe** in a new window
* clear the `result section`
* delete the current paragraph
### Note toolbar
At the top of the note, you can find a toolbar which exposes command buttons as well as configuration, security and display options
<img src="/assets/themes/zeppelin/img/ui-img/note_toolbar.png" />
On the far right is displayed the note name, just click on it to reveal the input form and update it
In the middle of the toolbar you can find the command buttons:
* execute all the paragraphs **sequentially**, in their display order
* hide/show `code section` of all paragraphs
* hide/show `result section` of all paragraphs
* clear the `result section` of all paragraphs
* clone the current note
* export the current note to a JSON file. _Please note that the `code section` and `result section` of all paragraphs will be exported. If you have heavy data in the `result section` of some paragraphs, it is recommended to clean them before exporting
* commit the current node content
* delete the note
* schedule the execution of **all paragraph** using a CRON syntax
<img src="/assets/themes/zeppelin/img/ui-img/note_commands.png" />
On the right of the note tool bar you can find configuration icons:
* display all the keyboard shorcuts
* configure the interpreters binding to the current note
* configure the note permissions
* switch the node display mode between `default`, `simple` and `report`
<img src="/assets/themes/zeppelin/img/ui-img/note_configuration.png" />

View file

@ -83,14 +83,14 @@ class PyZeppelinContext(dict):
def select(self, name, options, defaultValue = ""):
# auto_convert to ArrayList doesn't match the method signature on JVM side
tuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
tuples = list(map(lambda items: self.__tupleToScalaTuple2(items), options))
iterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(tuples)
return self.z.select(name, defaultValue, iterables)
def checkbox(self, name, options, defaultChecked = None):
if defaultChecked is None:
defaultChecked = map(lambda items: items[0], options)
optionTuples = map(lambda items: self.__tupleToScalaTuple2(items), options)
defaultChecked = list(map(lambda items: items[0], options))
optionTuples = list(map(lambda items: self.__tupleToScalaTuple2(items), options))
optionIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(optionTuples)
defaultCheckedIterables = gateway.jvm.scala.collection.JavaConversions.collectionAsScalaIterable(defaultChecked)

View file

@ -665,14 +665,29 @@ public class NotebookRestApi {
}
/**
* Search for a Notes
* Search for a Notes with permissions
*/
@GET
@Path("search")
public Response search(@QueryParam("q") String queryTerm) {
LOG.info("Searching notebooks for: {}", queryTerm);
String principal = SecurityUtils.getPrincipal();
HashSet<String> roles = SecurityUtils.getRoles();
HashSet<String> userAndRoles = new HashSet<String>();
userAndRoles.add(principal);
userAndRoles.addAll(roles);
List<Map<String, String>> notebooksFound = notebookIndex.query(queryTerm);
LOG.info("{} notbooks found", notebooksFound.size());
for (int i = 0; i < notebooksFound.size(); i++) {
String[] Id = notebooksFound.get(i).get("id").split("/", 2);
String noteId = Id[0];
if (!notebookAuthorization.isOwner(noteId, userAndRoles) &&
!notebookAuthorization.isReader(noteId, userAndRoles) &&
!notebookAuthorization.isWriter(noteId, userAndRoles)) {
notebooksFound.remove(i);
i--;
}
}
LOG.info("{} notebooks found", notebooksFound.size());
return new JsonResponse<>(Status.OK, notebooksFound).build();
}

View file

@ -238,7 +238,7 @@ public class ZeppelinServer extends Application {
webapp.setInitParameter("shiroConfigLocations",
new File(conf.getShiroPath()).toURI().toString());
webapp.addFilter(org.apache.shiro.web.servlet.ShiroFilter.class, "/*",
webapp.addFilter(org.apache.shiro.web.servlet.ShiroFilter.class, "/api/*",
EnumSet.allOf(DispatcherType.class));
webapp.addEventListener(new org.apache.shiro.web.env.EnvironmentLoaderListener());

View file

@ -133,7 +133,7 @@ public class NotebookServer extends WebSocketServlet implements
/** Lets be elegant here */
switch (messagereceived.op) {
case LIST_NOTES:
broadcastNoteList();
unicastNoteList(conn);
break;
case RELOAD_NOTES_FROM_REPO:
broadcastReloadedNoteList();
@ -201,7 +201,6 @@ public class NotebookServer extends WebSocketServlet implements
checkpointNotebook(conn, notebook, messagereceived);
break;
default:
broadcastNoteList();
break;
}
} catch (Exception e) {
@ -340,6 +339,14 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private void unicast(Message m, NotebookSocket conn) {
try {
conn.send(serializeMessage(m));
} catch (IOException e) {
LOG.error("socket error", e);
}
}
public List<Map<String, String>> generateNotebooksInfo(boolean needsReload) {
Notebook notebook = notebook();
@ -382,6 +389,11 @@ public class NotebookServer extends WebSocketServlet implements
broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo));
}
public void unicastNoteList(NotebookSocket conn) {
List<Map<String, String>> notesInfo = generateNotebooksInfo(false);
unicast(new Message(OP.NOTES_INFO).put("notes", notesInfo), conn);
}
public void broadcastReloadedNoteList() {
List<Map<String, String>> notesInfo = generateNotebooksInfo(true);
broadcastAll(new Message(OP.NOTES_INFO).put("notes", notesInfo));
@ -415,7 +427,6 @@ public class NotebookServer extends WebSocketServlet implements
if (note != null) {
if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId));
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
@ -437,7 +448,6 @@ public class NotebookServer extends WebSocketServlet implements
NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
permissionError(conn, "read", userAndRoles, notebookAuthorization.getReaders(noteId));
broadcastNoteList();
return;
}
addConnectionToNote(note.id(), conn);
@ -463,6 +473,12 @@ public class NotebookServer extends WebSocketServlet implements
return;
}
NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
permissionError(conn, "update", userAndRoles, notebookAuthorization.getWriters(noteId));
return;
}
Note note = notebook.getNote(noteId);
if (note != null) {
boolean cronUpdated = isCronUpdated(config, note.getConfig());

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.rest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -690,5 +691,70 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
ZeppelinServer.notebook.removeNote(note.getId());
}
@Test
public void testSearch() throws IOException {
Map<String, String> body;
GetMethod getSecurityTicket = httpGet("/security/ticket");
getSecurityTicket.addRequestHeader("Origin", "http://localhost");
Map<String, Object> respSecurityTicket = gson.fromJson(getSecurityTicket.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
body = (Map<String, String>) respSecurityTicket.get("body");
String username = body.get("principal");
getSecurityTicket.releaseConnection();
Note note1 = ZeppelinServer.notebook.createNote();
String jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 1\"}";
PostMethod postNotebookText = httpPost("/notebook/" + note1.getId() + "/paragraph", jsonRequest);
postNotebookText.releaseConnection();
Note note2 = ZeppelinServer.notebook.createNote();
jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 2\"}";
postNotebookText = httpPost("/notebook/" + note2.getId() + "/paragraph", jsonRequest);
postNotebookText.releaseConnection();
String jsonPermissions = "{\"owners\":[\"" + username + "\"],\"readers\":[\"" + username + "\"],\"writers\":[\"" + username + "\"]}";
PutMethod putPermission = httpPut("/notebook/" + note1.getId() + "/permissions", jsonPermissions);
putPermission.releaseConnection();
jsonPermissions = "{\"owners\":[\"admin\"],\"readers\":[\"admin\"],\"writers\":[\"admin\"]}";
putPermission = httpPut("/notebook/" + note2.getId() + "/permissions", jsonPermissions);
putPermission.releaseConnection();
GetMethod searchNotebook = httpGet("/notebook/search?q='ThisIsToTestSearchMethodWithPermissions'");
searchNotebook.addRequestHeader("Origin", "http://localhost");
Map<String, Object> respSearchResult = gson.fromJson(searchNotebook.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
ArrayList searchBody = (ArrayList) respSearchResult.get("body");
assertEquals("At-least one search results is there", true, searchBody.size() >= 1);
for (int i = 0; i < searchBody.size(); i++) {
Map<String, String> searchResult = (Map<String, String>) searchBody.get(i);
String userId = searchResult.get("id").split("/", 2)[0];
GetMethod getPermission = httpGet("/notebook/" + userId + "/permissions");
getPermission.addRequestHeader("Origin", "http://localhost");
Map<String, Object> resp = gson.fromJson(getPermission.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, ArrayList> permissions = (Map<String, ArrayList>) resp.get("body");
ArrayList owners = permissions.get("owners");
ArrayList readers = permissions.get("readers");
ArrayList writers = permissions.get("writers");
if (owners.size() != 0 && readers.size() != 0 && writers.size() != 0) {
assertEquals("User has permissions ", true, (owners.contains(username) || readers.contains(username) ||
writers.contains(username)));
}
getPermission.releaseConnection();
}
searchNotebook.releaseConnection();
ZeppelinServer.notebook.removeNote(note1.getId());
ZeppelinServer.notebook.removeNote(note2.getId());
}
}

View file

@ -76,4 +76,8 @@ angular.module('zeppelinWebApp').controller('HomeCtrl', function($scope, noteboo
node.hidden = !node.hidden;
};
$rootScope.noteName = function(note) {
return arrayOrderingSrv.getNoteName(note);
};
});

View file

@ -15,12 +15,12 @@ limitations under the License.
<script type="text/ng-template" id="notebook_folder_renderer.html">
<div ng-if="node.children == null">
<a style="text-decoration: none;" href="#/notebook/{{node.id}}">
<i style="font-size: 10px;" class="icon-doc"/> {{node.name || 'Note ' + node.id}}
<i style="font-size: 10px;" class="icon-doc"/> {{noteName(node)}}
</a>
</div>
<div ng-if="node.children != null">
<a style="text-decoration: none; cursor: pointer;" ng-click="toggleFolderNode(node)">
<i style="font-size: 10px;" ng-class="node.hidden ? 'icon-folder' : 'icon-folder-alt'" /> {{node.name}}
<i style="font-size: 10px;" ng-class="node.hidden ? 'icon-folder' : 'icon-folder-alt'" /> {{noteName(node)}}
</a>
<div ng-if="!node.hidden">
<ul style="list-style-type: none; padding-left:15px;">
@ -59,13 +59,17 @@ limitations under the License.
<i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
<ul id="notebook-names">
<li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
<li ng-repeat="note in home.notes.list | filter:query | orderBy:home.arrayOrderingSrv.notebookListOrdering track by $index">
<i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{noteName(note)}}</a>
</li>
<div ng-if="!query || query.name === ''">
<li ng-repeat="node in home.notes.root.children" ng-include="'notebook_folder_renderer.html'" />
<li ng-repeat="node in home.notes.root.children | orderBy:home.arrayOrderingSrv.notebookListOrdering track by $index" ng-include="'notebook_folder_renderer.html'" />
</div>
<div ng-if="query && query.name !== ''">
<li ng-repeat="note in home.notes.flatList | filter:query | orderBy:home.arrayOrderingSrv.notebookListOrdering track by $index">
<i style="font-size: 10px;" class="icon-doc"></i>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}}</a>
<a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{noteName(note)}}</a>
</li>
</div>
</ul>

View file

@ -13,9 +13,9 @@ limitations under the License.
-->
<div class="noteAction" ng-show="note.id && !paragraphUrl">
<h3>
<input type="text" pu-elastic-input class="form-control2" placeholder="{{note.name || 'Note ' + note.id}}" style="min-width: 200px; max-width: 600px;"
<input type="text" pu-elastic-input class="form-control2" placeholder="{{noteName(note)}}" style="min-width: 200px; max-width: 600px;"
ng-show="showEditor" ng-model="note.name" ng-blur="sendNewName();showEditor = false;" ng-enter="sendNewName();showEditor = false;" ng-escape="note.name = oldName; showEditor = false" focus-if="showEditor" />
<p class="form-control-static2" ng-click="showEditor = true; oldName = note.name" ng-show="!showEditor">{{note.name || 'Note ' + note.id}}</p>
<p class="form-control-static2" ng-click="showEditor = true; oldName = note.name" ng-show="!showEditor">{{noteName(note)}}</p>
<span class="labelBtn btn-group">
<button type="button"
class="btn btn-default btn-xs"

View file

@ -386,6 +386,7 @@ angular.module('zeppelinWebApp')
}
/** push the rest */
$scope.paragraph.authenticationInfo = data.paragraph.authenticationInfo;
$scope.paragraph.aborted = data.paragraph.aborted;
$scope.paragraph.dateUpdated = data.paragraph.dateUpdated;
$scope.paragraph.dateCreated = data.paragraph.dateCreated;
@ -967,7 +968,12 @@ angular.module('zeppelinWebApp')
}
return '';
}
var desc = 'Took ' + (timeMs/1000) + ' seconds';
var user = 'anonymous';
if (pdata.authenticationInfo !== null && pdata.authenticationInfo.user !== null) {
user = pdata.authenticationInfo.user;
}
var dateUpdated = (pdata.dateUpdated === null) ? 'unknown' : pdata.dateUpdated;
var desc = 'Took ' + (timeMs/1000) + ' seconds. Last updated by ' + user + ' at time ' + dateUpdated + '.';
if ($scope.isResultOutdated()){
desc += ' (outdated)';
}

View file

@ -21,7 +21,7 @@ limitations under the License.
<i style="font-size: 10px;" class="icon-doc"></i>
<a class="search-results-header"
href="#/notebook/{{note.id}}">
{{note.name || 'Note ' + note.id}}
{{note.name.trim()==='' && 'Note ' + note.id.split('/',2)[0] || note.name}}
</a>
</h4>
</div>

View file

@ -15,8 +15,18 @@
angular.module('zeppelinWebApp').service('arrayOrderingSrv', function() {
var arrayOrderingSrv = this;
this.notebookListOrdering = function(note) {
return (note.name ? note.name : 'Note ' + note.id);
return arrayOrderingSrv.getNoteName(note);
};
this.getNoteName = function(note) {
if(note.name === undefined || note.name.trim() === '') {
return'Note ' + note.id;
} else {
return note.name;
}
};
});

View file

@ -12,10 +12,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/ng-template" id="notebook_list_renderer.html">
<a ng-if="note.id" href="#/notebook/{{note.id}}">{{note.name || 'Note ' + note.id}} </a>
<a ng-if="note.id" href="#/notebook/{{note.id}}">{{noteName(note)}} </a>
<li ng-if="!note.id"
class="dropdown-submenu">
<a tabindex="-1" href="javascript: void(0)">{{note.name}}</a>
<a tabindex="-1" href="javascript: void(0)">{{noteName(note)}}</a>
<ul class="dropdown-menu">
<li ng-repeat="note in note.children track by $index" ng-class="{'active' : navbar.isActive(note.id)}" ng-include="'notebook_list_renderer.html'">
</li>
@ -44,6 +44,10 @@ limitations under the License.
<li><a href="" data-toggle="modal" data-target="#noteNameModal"><i class="fa fa-plus"></i> Create new note</a></li>
<li class="divider"></li>
<div id="notebook-list" class="scrollbar-container">
<li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
<li ng-repeat="note in navbar.notes.list | filter:query | orderBy:navbar.arrayOrderingSrv.notebookListOrdering track by $index"
ng-class="{'active' : navbar.isActive(note.id)}">
<a href="#/notebook/{{note.id}}">{{noteName(note)}} </a>
<li ng-repeat="note in navbar.notes.root.children track by $index" ng-class="{'active' : navbar.isActive(note.id)}" ng-include="'notebook_list_renderer.html'">
</li>
</div>

View file

@ -61,6 +61,14 @@ module.exports = function(config) {
'bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js',
'bower_components/floatThead/dist/jquery.floatThead.js',
'bower_components/floatThead/dist/jquery.floatThead.min.js',
'bower_components/datatables.net/js/jquery.dataTables.js',
'bower_components/datatables.net-bs/js/dataTables.bootstrap.js',
'bower_components/datatables.net-buttons/js/dataTables.buttons.js',
'bower_components/datatables.net-buttons/js/buttons.colVis.js',
'bower_components/datatables.net-buttons/js/buttons.flash.js',
'bower_components/datatables.net-buttons/js/buttons.html5.js',
'bower_components/datatables.net-buttons/js/buttons.print.js',
'bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.js',
'bower_components/angular-mocks/angular-mocks.js',
// endbower
'src/app/app.js',