Merge branch 'master' into jdbc-impersonation

This commit is contained in:
astroshim 2016-11-11 19:03:38 +09:00
commit 79ba25b56d
72 changed files with 4910 additions and 1212 deletions

4
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.class
*.pyc
# Package Files #
*.jar
@ -6,7 +7,8 @@
*.ear
# interpreter
/interpreter/
/interpreter/*
!/interpreter/lib
# interpreter temp files
spark/derby.log

View file

@ -45,6 +45,10 @@ user3 = password4, role2
#ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
#ldapRealm.contextFactory.authenticationMechanism = SIMPLE
### A sample PAM configuration
#pamRealm=org.apache.zeppelin.realm.PamRealm
#pamRealm.service=sshd
### A sample for configuring ZeppelinHub Realm
#zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm
## Url of ZeppelinHub

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -39,6 +39,13 @@ With `%html` directive, Zeppelin treats your output as HTML
<img src="/assets/themes/zeppelin/img/screenshots/display_html.png" />
### Mathematical expressions
HTML display system automatically formats mathematical expression using [MathJax](https://www.mathjax.org/). You can use
`\\( INLINE EXPRESSION \\)` and `$$ EXPRESSION $$` to format. For example
<img src="/assets/themes/zeppelin/img/screenshots/display_formula.png" />
## Table
If you have data that row separated by '\n' (newline) and column separated by '\t' (tab) with first row as header row, for example

View file

@ -33,8 +33,8 @@ You can use Zeppelin to retrieve distributed data from cache using Ignite SQL in
## Installing and Running Ignite example
In order to use Ignite interpreters, you may install Apache Ignite in some simple steps:
1. Download Ignite [source release](https://ignite.apache.org/download.html#sources) or [binary release](https://ignite.apache.org/download.html#binaries) whatever you want. But you must download Ignite as the same version of Zeppelin's. If it is not, you can't use scala code on Zeppelin. You can find ignite version in Zeppelin at the pom.xml which is placed under `path/to/your-Zeppelin/ignite/pom.xml` ( Of course, in Zeppelin source release ). Please check `ignite.version` .<br>Currently, Zeppelin provides ignite only in Zeppelin source release. So, if you download Zeppelin binary release( `zeppelin-0.5.0-incubating-bin-spark-xxx-hadoop-xx` ), you can not use ignite interpreter on Zeppelin. We are planning to include ignite in a future binary release.
2. Examples are shipped as a separate Maven project, so to start running you simply need to import provided <dest_dir>/apache-ignite-fabric-1.2.0-incubating-bin/pom.xml file into your favourite IDE, such as Eclipse.
1. Ignite provides examples only with source or binary release. Download Ignite [source release](https://ignite.apache.org/download.html#sources) or [binary release](https://ignite.apache.org/download.html#binaries) whatever you want. But you must download Ignite as the same version of Zeppelin's. If it is not, you can't use scala code on Zeppelin. The supported Ignite version is specified in [Supported Interpreter table](https://zeppelin.apache.org/supported_interpreters.html#ignite) for each Zeppelin release. If you're using Zeppelin master branch, please see `ignite.version` in `path/to/your-Zeppelin/ignite/pom.xml`.
2. Examples are shipped as a separate Maven project, so to start running you simply need to import provided `<dest_dir>/apache-ignite-fabric-{version}-bin/examples/pom.xml` file into your favourite IDE, such as Eclipse.
* In case of Eclipse, Eclipse -> File -> Import -> Existing Maven Projects
* Set examples directory path to Eclipse and select the pom.xml.
@ -58,12 +58,12 @@ At the "Interpreters" menu, you may edit Ignite interpreter or create new one. Z
<tr>
<td>ignite.addresses</td>
<td>127.0.0.1:47500..47509</td>
<td>Coma separated list of Ignite cluster hosts. See [Ignite Cluster Configuration](https://apacheignite.readme.io/v1.2/docs/cluster-config) section for more details.</td>
<td>Coma separated list of Ignite cluster hosts. See [Ignite Cluster Configuration](https://apacheignite.readme.io/docs/cluster-config) section for more details.</td>
</tr>
<tr>
<td>ignite.clientMode</td>
<td>true</td>
<td>You can connect to the Ignite cluster as client or server node. See [Ignite Clients vs. Servers](https://apacheignite.readme.io/v1.2/docs/clients-vs-servers) section for details. Use true or false values in order to connect in client or server mode respectively.</td>
<td>You can connect to the Ignite cluster as client or server node. See [Ignite Clients vs. Servers](https://apacheignite.readme.io/docs/clients-vs-servers) section for details. Use true or false values in order to connect in client or server mode respectively.</td>
</tr>
<tr>
<td>ignite.config.url</td>
@ -78,7 +78,7 @@ At the "Interpreters" menu, you may edit Ignite interpreter or create new one. Z
<tr>
<td>ignite.peerClassLoadingEnabled</td>
<td>true</td>
<td>Enables peer-class-loading. See [Zero Deployment](https://apacheignite.readme.io/v1.2/docs/zero-deployment) section for details. Use true or false values in order to enable or disable P2P class loading respectively.</td>
<td>Enables peer-class-loading. See [Zero Deployment](https://apacheignite.readme.io/docs/zero-deployment) section for details. Use true or false values in order to enable or disable P2P class loading respectively.</td>
</tr>
</table>
@ -89,7 +89,7 @@ After configuring Ignite interpreter, create your own notebook. Then you can bin
![Binding Interpreters](../assets/themes/zeppelin/img/docs-img/ignite-interpreter-binding.png)
For more interpreter binding information see [here](http://zeppelin.apache.org/docs/manual/interpreters.html).
For more interpreter binding information see [here](../manual/interpreters.html#what-is-interpreter-setting).
### Ignite SQL interpreter
In order to execute SQL query, use ` %ignite.ignitesql ` prefix. <br>

220
docs/interpreter/mahout.md Normal file
View file

@ -0,0 +1,220 @@
---
layout: page
title: "Mahout Interpreter for Apache Zeppelin"
description: "Apache Mahout provides a unified API (the R-Like Scala DSL) for quickly creating machine learning algorithms on a variety of engines."
group: interpreter
---
<!--
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 %}
# Apache Mahout Interpreter for Apache Zeppelin
<div id="toc"></div>
## Installation
Apache Mahout is a collection of packages that enable machine learning and matrix algebra on underlying engines such as Apache Flink or Apache Spark. A convenience script for creating and configuring two Mahout enabled interpreters exists. The `%sparkMahout` and `%flinkMahout` interpreters do not exist by default but can be easily created using this script.
### Easy Installation
To quickly and easily get up and running using Apache Mahout, run the following command from the top-level directory of the Zeppelin install:
```bash
python scripts/mahout/add_mahout.py
```
This will create the `%sparkMahout` and `%flinkMahout` interpreters, and restart Zeppelin.
### Advanced Installation
The `add_mahout.py` script contains several command line arguments for advanced users.
<table class="table-configuration">
<tr>
<th>Argument</th>
<th>Description</th>
<th>Example</th>
</tr>
<tr>
<td>--zeppelin_home</td>
<td>This is the path to the Zeppelin installation. This flag is not needed if the script is run from the top-level installation directory or from the `zeppelin/scripts/mahout` directory.</td>
<td>/path/to/zeppelin</td>
</tr>
<tr>
<td>--mahout_home</td>
<td>If the user has already installed Mahout, this flag can set the path to `MAHOUT_HOME`. If this is set, downloading Mahout will be skipped.</td>
<td>/path/to/mahout_home</td>
</tr>
<tr>
<td>--restart_later</td>
<td>Restarting is necessary for updates to take effect. By default the script will restart Zeppelin for you- restart will be skipped if this flag is set.</td>
<td>NA</td>
</tr>
<tr>
<td>--force_download</td>
<td>This flag will force the script to re-download the binary even if it already exists. This is useful for previously failed downloads.</td>
<td>NA</td>
</tr>
<tr>
<td>--overwrite_existing</td>
<td>This flag will force the script to overwrite existing `%sparkMahout` and `%flinkMahout` interpreters. Useful when you want to just start over.</td>
<td>NA</td>
</tr>
</table>
__NOTE 1:__ Apache Mahout at this time only supports Spark 1.5 and Spark 1.6 and Scala 2.10. If the user is using another version of Spark (e.g. 2.0), the `%sparkMahout` will likely not work. The `%flinkMahout` interpreter will still work and the user is encouraged to develop with that engine as the code can be ported via copy and paste, as is evidenced by the tutorial notebook.
__NOTE 2:__ If using Apache Flink in cluster mode, the following libraries will also need to be coppied to `${FLINK_HOME}/lib`
- mahout-math-0.12.2.jar
- mahout-math-scala_2.10-0.12.2.jar
- mahout-flink_2.10-0.12.2.jar
- mahout-hdfs-0.12.2.jar
- [com.google.guava:guava:14.0.1](http://central.maven.org/maven2/com/google/guava/guava/14.0.1/guava-14.0.1.jar)
## Overview
The [Apache Mahout](http://mahout.apache.org/)™ project's goal is to build an environment for quickly creating scalable performant machine learning applications.
Apache Mahout software provides three major features:
- A simple and extensible programming environment and framework for building scalable algorithms
- A wide variety of premade algorithms for Scala + Apache Spark, H2O, Apache Flink
- Samsara, a vector math experimentation environment with R-like syntax which works at scale
In other words:
*Apache Mahout provides a unified API for quickly creating machine learning algorithms on a variety of engines.*
## How to use
When starting a session with Apache Mahout, depending on which engine you are using (Spark or Flink), a few imports must be made and a _Distributed Context_ must be declared. Copy and paste the following code and run once to get started.
### Flink
```scala
%flinkMahout
import org.apache.flink.api.scala._
import org.apache.mahout.math.drm._
import org.apache.mahout.math.drm.RLikeDrmOps._
import org.apache.mahout.flinkbindings._
import org.apache.mahout.math._
import scalabindings._
import RLikeOps._
implicit val ctx = new FlinkDistributedContext(benv)
```
### Spark
```scala
%sparkMahout
import org.apache.mahout.math._
import org.apache.mahout.math.scalabindings._
import org.apache.mahout.math.drm._
import org.apache.mahout.math.scalabindings.RLikeOps._
import org.apache.mahout.math.drm.RLikeDrmOps._
import org.apache.mahout.sparkbindings._
implicit val sdc: org.apache.mahout.sparkbindings.SparkDistributedContext = sc2sdc(sc)
```
### Same Code, Different Engines
After importing and setting up the distributed context, the Mahout R-Like DSL is consistent across engines. The following code will run in both `%flinkMahout` and `%sparkMahout`
```scala
val drmData = drmParallelize(dense(
(2, 2, 10.5, 10, 29.509541), // Apple Cinnamon Cheerios
(1, 2, 12, 12, 18.042851), // Cap'n'Crunch
(1, 1, 12, 13, 22.736446), // Cocoa Puffs
(2, 1, 11, 13, 32.207582), // Froot Loops
(1, 2, 12, 11, 21.871292), // Honey Graham Ohs
(2, 1, 16, 8, 36.187559), // Wheaties Honey Gold
(6, 2, 17, 1, 50.764999), // Cheerios
(3, 2, 13, 7, 40.400208), // Clusters
(3, 3, 13, 4, 45.811716)), numPartitions = 2)
drmData.collect(::, 0 until 4)
val drmX = drmData(::, 0 until 4)
val y = drmData.collect(::, 4)
val drmXtX = drmX.t %*% drmX
val drmXty = drmX.t %*% y
val XtX = drmXtX.collect
val Xty = drmXty.collect(::, 0)
val beta = solve(XtX, Xty)
```
## Leveraging Resource Pools and R for Visualization
Resource Pools are a powerful Zeppelin feature that lets us share information between interpreters. A fun trick is to take the output of our work in Mahout and analyze it in other languages.
### Setting up a Resource Pool in Flink
In Spark based interpreters resource pools are accessed via the ZeppelinContext API. To put and get things from the resource pool one can be done simple
```scala
val myVal = 1
z.put("foo", myVal)
val myFetchedVal = z.get("foo")
```
To add this functionality to a Flink based interpreter we declare the follwoing
```scala
%flinkMahout
import org.apache.zeppelin.interpreter.InterpreterContext
val z = InterpreterContext.get().getResourcePool()
```
Now we can access the resource pool in a consistent manner from the `%flinkMahout` interpreter.
### Passing a variable from Mahout to R and Plotting
In this simple example, we use Mahout (on Flink or Spark, the code is the same) to create a random matrix and then take the Sin of each element. We then randomly sample the matrix and create a tab separated string. Finally we pass that string to R where it is read as a .tsv file, and a DataFrame is created and plotted using native R plotting libraries.
```scala
val mxRnd = Matrices.symmetricUniformView(5000, 2, 1234)
val drmRand = drmParallelize(mxRnd)
val drmSin = drmRand.mapBlock() {case (keys, block) =>
val blockB = block.like()
for (i <- 0 until block.nrow) {
blockB(i, 0) = block(i, 0)
blockB(i, 1) = Math.sin((block(i, 0) * 8))
}
keys -> blockB
}
z.put("sinDrm", org.apache.mahout.math.drm.drmSampleToTSV(drmSin, 0.85))
```
And then in an R paragraph...
```r
%spark.r {"imageWidth": "400px"}
library("ggplot2")
sinStr = z.get("flinkSinDrm")
data <- read.table(text= sinStr, sep="\t", header=FALSE)
plot(data, col="red")
```

View file

@ -53,6 +53,12 @@ The following example demonstrates the basic usage of Markdown in a Zeppelin not
<img src="../assets/themes/zeppelin/img/docs-img/markdown-example.png" width="70%" />
## Mathematical expression
Markdown interpreter leverages %html display system internally. That means you can mix mathematical expressions with markdown syntax. For more information, please see [Mathematical Expression](../displaysystem/basicdisplaysystem.html#mathematical-expressions) section.
### Markdown4j Parser
`markdown4j` parser provides [YUML](http://yuml.me/) and [Websequence](https://www.websequencediagrams.com/) extensions

View file

@ -86,9 +86,26 @@ print("".join(z.checkbox("f3", [("o1","1"), ("o2","2")],["1"])))
* Code-completion is currently not implemented.
## Matplotlib integration
The python interpreter can display matplotlib graph with the function `z.show()`.
You need to have matplotlib module installed and a XServer running to use this functionality!
The python interpreter can display matplotlib figures inline automatically using the `pyplot` module:
```python
%python
import matplotlib.pyplot as plt
plt.plot([1, 2, 3])
```
This is the recommended method for using matplotlib from within a Zeppelin notebook. The output of this command will by default be converted to HTML by implicitly making use of the `%html` magic. Additional configuration can be achieved using the builtin `z.configure_mpl()` method. For example,
```python
z.configure_mpl(width=400, height=300, fmt='svg')
plt.plot([1, 2, 3])
```
Will produce a 400x300 image in SVG format, which by default are normally 600x400 and PNG respectively. In the future, another option called `angular` can be used to make it possible to update a plot produced from one paragraph directly from another (the output will be `%angular` instead of `%html`). However, this feature is already available in the `pyspark` interpreter. More details can be found in the included "Zeppelin Tutorial: Python - matplotlib basic" tutorial notebook.
If Zeppelin cannot find the matplotlib backend files (which should usually be found in `$ZEPPELIN_HOME/interpreter/lib/python`) in your `PYTHONPATH`, then the backend will automatically be set to agg, and the (otherwise deprecated) instructions below can be used for more limited inline plotting.
If you are unable to load the inline backend, use `z.show(plt)`:
```python
%python
import matplotlib.pyplot as plt

View file

@ -363,6 +363,11 @@ select * from ${table=defaultTableName} where text like '%${search}%'
To learn more about dynamic form, checkout [Dynamic Form](../manual/dynamicform.html).
## Matplotlib Integration (pyspark)
Both the `python` and `pyspark` interpreters have built-in support for inline visualization using `matplotlib`, a popular plotting library for python. More details can be found in the [python interpreter documentation](../interpreter/python.html), since matplotlib support is identical. More advanced interactive plotting can be done with pyspark through utilizing Zeppelin's built-in [Angular Display System](../displaysystem/back-end-angular.html), as shown below:
<img class="img-responsive" src="../assets/themes/zeppelin/img/docs-img/matplotlibAngularExample.gif" />
## Interpreter setting option
You can choose one of `shared`, `scoped` and `isolated` options wheh you configure Spark interpreter. Spark interpreter creates separated Scala compiler per each notebook but share a single SparkContext in `scoped` mode (experimental). It creates separated SparkContext per each notebook in `isolated` mode.

View file

@ -808,6 +808,127 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
</tr>
</table>
<br/>
### Update paragraph configuration
<table class="table-configuration">
<col width="200">
<tr>
<td>Description</td>
<td>This ```PUT``` method update paragraph configuration using given id so that user can change paragraph setting such as graph type, show or hide editor/result and paragraph size, etc. You can update certain fields you want, for example you can update <code>colWidth</code> field only by sending request with payload <code>{"colWidth": 12.0}</code>.
</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/[noteId]/paragraph/[paragraphId]/config```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td>Bad Request code</td>
<td>400</td>
</tr>
<tr>
<td>Forbidden code</td>
<td>403</td>
</tr>
<tr>
<td>Not Found code</td>
<td>404</td>
</tr>
<tr>
<td>Fail code</td>
<td>500</td>
</tr>
<tr>
<td>sample JSON input</td>
<td><pre>
{
"colWidth": 6.0,
"graph": {
"mode": "lineChart",
"height": 200.0,
"optionOpen": false,
"keys": [
{
"name": "age",
"index": 0.0,
"aggr": "sum"
}
],
"values": [
{
"name": "value",
"index": 1.0,
"aggr": "sum"
}
],
"groups": [],
"scatter": {}
},
"editorHide": true,
"editorMode": "ace/mode/markdown",
"tableHide": false
}</pre></td>
</tr>
<tr>
<td>sample JSON response</td>
<td><pre>
{
"status":"OK",
"message":"",
"body":{
"text":"%sql \nselect age, count(1) value\nfrom bank \nwhere age \u003c 30 \ngroup by age \norder by age",
"config":{
"colWidth":6.0,
"graph":{
"mode":"lineChart",
"height":200.0,
"optionOpen":false,
"keys":[
{
"name":"age",
"index":0.0,
"aggr":"sum"
}
],
"values":[
{
"name":"value",
"index":1.0,
"aggr":"sum"
}
],
"groups":[],
"scatter":{}
},
"tableHide":false,
"editorMode":"ace/mode/markdown",
"editorHide":true
},
"settings":{
"params":{},
"forms":{}
},
"apps":[],
"jobName":"paragraph_1423500782552_-1439281894",
"id":"20150210-015302_1492795503",
"result":{
"code":"SUCCESS",
"type":"TABLE",
"msg":"age\tvalue\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n"
},
"dateCreated":"Feb 10, 2015 1:53:02 AM",
"dateStarted":"Jul 3, 2015 1:43:17 PM",
"dateFinished":"Jul 3, 2015 1:43:23 PM",
"status":"FINISHED",
"progressUpdateIntervalMs":500
}
}</pre></td>
</tr>
</table>
<br/>
### Move a paragraph to the specific index
<table class="table-configuration">
@ -835,7 +956,6 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
</tr>
</table>
<br/>
### Delete a paragraph
<table class="table-configuration">
@ -934,7 +1054,8 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
<td> Fail code</td>
<td> 500 </td>
</tr>
<td>sample JSON input</td>
<tr>
<td>sample JSON input</td>
<td><pre>
{
"paragraphs": [
@ -961,6 +1082,7 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
"config": {},
"info": {}
}</pre></td>
</tr>
<tr>
<td>sample JSON response</td>
<td><pre>
@ -970,7 +1092,6 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
"body": "2AZPHY918"
}</pre></td>
</tr>
</tr>
</table>
<br />

View file

@ -147,6 +147,19 @@ ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
ldapRealm.contextFactory.authenticationMechanism = SIMPLE
```
### PAM
[PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) authentication support allows the reuse of existing authentication
moduls on the host where Zeppelin is running. On a typical system modules are configured per service for example sshd, passwd, etc. under `/etc/pam.d/`. You can
either reuse one of these services or create your own for Zeppelin. Activiting PAM authentication requires two parameters:
1. realm: The Shiro realm being used
2. service: The service configured under `/etc/pam.d/` to be used. The name here needs to be the same as the file name under `/etc/pam.d/`
```
[main]
pamRealm=org.apache.zeppelin.realm.PamRealm
pamRealm.service=sshd
```
### ZeppelinHub
[ZeppelinHub](https://www.zeppelinhub.com) is a service that synchronize your Apache Zeppelin notebooks and enables you to collaborate easily.

View file

@ -27,8 +27,12 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import org.apache.flink.api.common.JobID;
import org.apache.flink.api.scala.FlinkILoop;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.runtime.akka.AkkaUtils;
import org.apache.flink.runtime.instance.ActorGateway;
import org.apache.flink.runtime.messages.JobManagerMessages;
import org.apache.flink.runtime.minicluster.LocalFlinkMiniCluster;
import org.apache.flink.runtime.util.EnvironmentInformation;
import org.apache.zeppelin.interpreter.Interpreter;
@ -47,10 +51,12 @@ import scala.Option;
import scala.Some;
import scala.collection.JavaConversions;
import scala.collection.immutable.Nil;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.AbstractFunction0;
import scala.tools.nsc.Settings;
import scala.tools.nsc.interpreter.IMain;
import scala.tools.nsc.interpreter.Results;
import scala.tools.nsc.settings.MutableSettings;
import scala.tools.nsc.settings.MutableSettings.BooleanSetting;
import scala.tools.nsc.settings.MutableSettings.PathSetting;
@ -180,7 +186,13 @@ public class FlinkInterpreter extends Interpreter {
BooleanSetting b = (BooleanSetting) settings.usejavacp();
b.v_$eq(true);
settings.scala$tools$nsc$settings$StandardScalaSettings$_setter_$usejavacp_$eq(b);
// To prevent 'File name too long' error on some file system.
MutableSettings.IntSetting numClassFileSetting = settings.maxClassfileName();
numClassFileSetting.v_$eq(128);
settings.scala$tools$nsc$settings$ScalaSettings$_setter_$maxClassfileName_$eq(
numClassFileSetting);
return settings;
}
@ -334,6 +346,20 @@ public class FlinkInterpreter extends Interpreter {
@Override
public void cancel(InterpreterContext context) {
if (localMode()) {
// In localMode we can cancel all running jobs,
// because the local cluster can only run one job at the time.
for (JobID job : this.localFlinkCluster.getCurrentlyRunningJobsJava()) {
logger.info("Stop job: " + job);
cancelJobLocalMode(job);
}
}
}
private void cancelJobLocalMode(JobID jobID){
FiniteDuration timeout = AkkaUtils.getTimeout(this.localFlinkCluster.configuration());
ActorGateway leader = this.localFlinkCluster.getLeaderGateway(timeout);
leader.ask(new JobManagerMessages.CancelJob(jobID), timeout);
}
@Override

View file

@ -0,0 +1,312 @@
# 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.
# This file provides a static (non-interactive) matplotlib plotting backend
# for zeppelin notebooks for use with the python/pyspark interpreters
from __future__ import print_function
import uuid
import warnings
import base64
from io import BytesIO
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import mpl_config
import matplotlib
from matplotlib._pylab_helpers import Gcf
from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg
from matplotlib.backend_bases import ShowBase, FigureManagerBase
from matplotlib.figure import Figure
########################################################################
#
# The following functions and classes are for pylab and implement
# window/figure managers, etc...
#
########################################################################
class Show(ShowBase):
"""
A callable object that displays the figures to the screen. Valid kwargs
include figure width and height (in units supported by the div tag), block
(allows users to override blocking behavior regardless of whether or not
interactive mode is enabled, currently unused) and close (Implicitly call
matplotlib.pyplot.close('all') with each call to show()).
"""
def __call__(self, close=None, block=None, **kwargs):
if close is None:
close = mpl_config.get('close')
try:
managers = Gcf.get_all_fig_managers()
if not managers:
return
# Tell zeppelin that the output will be html using the %html magic
# We want to do this only once to avoid seeing "%html" printed
# directly to the outout when multiple figures are displayed from
# one paragraph.
if mpl_config.get('angular'):
print('%angular')
else:
print('%html')
# Show all open figures
for manager in managers:
manager.show(**kwargs)
finally:
# This closes all the figures if close is set to True.
if close and Gcf.get_all_fig_managers():
Gcf.destroy_all()
class FigureCanvasZInline(FigureCanvasAgg):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
"""
def get_bytes(self, **kwargs):
"""
Get the byte representation of the figure.
Should only be used with jpg/png formats.
"""
# Make sure format is correct
fmt = kwargs.get('format', mpl_config.get('format'))
if fmt == 'svg':
raise ValueError("get_bytes() does not support svg, use png or jpg")
# Express the image as bytes
buf = BytesIO()
self.print_figure(buf, **kwargs)
byte_str = b"data:image/%s;base64," %fmt
byte_str += base64.b64encode(buf.getvalue())
# Python3 forces all strings to default to unicode, but for raster image
# formats (eg png, jpg), we want to work with bytes. Thus this step is
# needed to ensure compatability for all python versions.
byte_str = byte_str.decode('ascii')
buf.close()
return byte_str
def get_svg(self, **kwargs):
"""
Get the svg representation of the figure.
Should only be used with svg format.
"""
# Make sure format is correct
fmt = kwargs.get('format', mpl_config.get('format'))
if fmt != 'svg':
raise ValueError("get_svg() does not support png or jpg, use svg")
# For SVG the data string has to be unicode, not bytes
buf = StringIO()
self.print_figure(buf, **kwargs)
svg_str = buf.getvalue()
buf.close()
return svg_str
def draw_idle(self, *args, **kwargs):
"""
Called when the figure gets updated (eg through a plotting command).
This is overriden to allow open figures to be reshown after they
are updated when mpl_config.get('close') is False.
"""
if not self._is_idle_drawing:
with self._idle_draw_cntx():
self.draw(*args, **kwargs)
draw_if_interactive()
class FigureManagerZInline(FigureManagerBase):
"""
Wrap everything up into a window for the pylab interface
"""
def __init__(self, canvas, num):
FigureManagerBase.__init__(self, canvas, num)
self.fig_id = "figure_{0}".format(uuid.uuid4().hex)
self._shown = False
def angular_bind(self, **kwargs):
"""
Bind figure data to Zeppelin's Angular Object Registry.
If mpl_config("angular") is True and PY4J is supported, this allows
for the possibility to interactively update a figure from a separate
paragraph without having to display it multiple times.
"""
# This doesn't work for SVG so make sure it's not our format
fmt = kwargs.get('format', mpl_config.get('format'))
if fmt == 'svg':
return
# Get the figure data as a byte array
src = self.canvas.get_bytes(**kwargs)
# Flag to determine whether or not to use
# zeppelin's angular display system
angular = mpl_config.get('angular')
# ZeppelinContext instance (requires PY4J)
context = mpl_config.get('context')
# Finally we must ensure that automatic closing is set to False,
# as otherwise using the angular display system is pointless
close = mpl_config.get('close')
# If above conditions are met, bind the figure data to
# the Angular Object Registry.
if not close and angular:
if hasattr(context, 'angularBind'):
# Binding is performed through figure ID to ensure this works
# if multiple figures are open
context.angularBind(self.fig_id, src)
# Zeppelin will automatically replace this value even if it
# is updated from another pargraph thanks to the {{}} notation
src = "{{%s}}" %self.fig_id
else:
warnings.warn("Cannot bind figure to Angular Object Registry. "
"Check if PY4J is installed.")
return src
def angular_unbind(self):
"""
Unbind figure from angular display system.
"""
context = mpl_config.get('context')
if hasattr(context, 'angularUnbind'):
context.angularUnbind(self.fig_id)
def destroy(self):
"""
Called when close=True or implicitly by pyplot.close().
Overriden to automatically clean up the angular object registry.
"""
self.angular_unbind()
def show(self, **kwargs):
if not self._shown:
zdisplay(self.canvas.figure, **kwargs)
else:
self.canvas.draw_idle()
self.angular_bind(**kwargs)
self._shown = True
def draw_if_interactive():
"""
If interactive mode is on, this allows for updating properties of
the figure when each new plotting command is called.
"""
manager = Gcf.get_active()
interactive = matplotlib.is_interactive()
angular = mpl_config.get('angular')
# Don't bother continuing if we aren't in interactive mode
# or if there are no active figures. Also pointless to continue
# in angular mode as we don't want to reshow the figure.
if not interactive or angular or manager is None:
return
# Allow for figure to be reshown if close is false since
# this function call implies that it has been updated
if not mpl_config.get('close'):
manager._shown = False
def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
"""
# if a main-level app must be created, this (and
# new_figure_manager_given_figure) is the usual place to
# do it -- see backend_wx, backend_wxagg and backend_tkagg for
# examples. Not all GUIs require explicit instantiation of a
# main-level app (egg backend_gtk, backend_gtkagg) for pylab
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)
def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasZInline(figure)
manager = FigureManagerZInline(canvas, num)
return manager
########################################################################
#
# Backend specific functions
#
########################################################################
def zdisplay(fig, **kwargs):
"""
Publishes a matplotlib figure to the notebook paragraph output.
"""
# kwargs can be width or height (in units supported by div tag)
width = kwargs.pop('width', 'auto')
height = kwargs.pop('height', 'auto')
fmt = kwargs.get('format', mpl_config.get('format'))
# Check if format is supported
supported_formats = mpl_config.get('supported_formats')
if fmt not in supported_formats:
raise ValueError("Unsupported format %s" %fmt)
# For SVG the data string has to be unicode, not bytes
if fmt == 'svg':
img = fig.canvas.get_svg(**kwargs)
# This is needed to ensure the SVG image is the correct size.
# We should find a better way to do this...
width = '{}px'.format(mpl_config.get('width'))
height = '{}px'.format(mpl_config.get('height'))
else:
# Express the image as bytes
src = fig.canvas.manager.angular_bind(**kwargs)
img = "<img src={src} style='width={width};height:{height}'>"
img = img.format(src=src, width=width, height=height)
# Print the image to the notebook paragraph via the %html magic
html = "<div style='width:{width};height:{height}'>{img}<div>"
print(html.format(width=width, height=height, img=img))
def displayhook():
"""
Called post paragraph execution if interactive mode is on
"""
if matplotlib.is_interactive():
show()
########################################################################
#
# Now just provide the standard names that backend.__init__ is expecting
#
########################################################################
# Create a reference to the show function we are using. This is what actually
# gets called by matplotlib.pyplot.show().
show = Show()
# Default FigureCanvas and FigureManager classes to use from the backend
FigureCanvas = FigureCanvasZInline
FigureManager = FigureManagerZInline

View file

@ -0,0 +1,95 @@
# 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.
# This module provides utitlites for users to configure the inline plotting
# backend through a PyZeppelinContext instance (eg, through z.configure_mpl())
import matplotlib
def configure(**kwargs):
"""
Generic configure function.
Usage: configure(prop1='foo', prop2='bar', ...)
Currently supported zeppelin-specific properties are:
interactive - If true show all figures without explicit call to show()
via a post-execute hook.
angular - If true, bind figures to angular display system.
close - If true, close all figures once shown.
width, height - Default width / height of the figure in pixels.
fontsize - Font size.
dpi - dpi of the figure.
fmt - Figure format
supported_formats - Supported Figure formats ()
context - ZeppelinContext instance (requires PY4J)
"""
_config.update(**kwargs)
# Broadcast relevant changes to matplotlib RC
_on_config_change()
def get(key):
"""
Get the configuration info given a key
"""
return _config[key]
def _on_config_change():
# dpi
dpi = _config['dpi']
matplotlib.rcParams['savefig.dpi'] = dpi
matplotlib.rcParams['figure.dpi'] = dpi
# Width and height
width = float(_config['width']) / dpi
height = float(_config['height']) / dpi
matplotlib.rcParams['figure.figsize'] = (width, height)
# Font size
fontsize = _config['fontsize']
matplotlib.rcParams['font.size'] = fontsize
# Default Figure Format
fmt = _config['format']
supported_formats = _config['supported_formats']
if fmt not in supported_formats:
raise ValueError("Unsupported format %s" %fmt)
matplotlib.rcParams['savefig.format'] = fmt
# Interactive mode
interactive = _config['interactive']
matplotlib.interactive(interactive)
def _init_config():
dpi = matplotlib.rcParams['savefig.dpi']
fmt = matplotlib.rcParams['savefig.format']
width, height = matplotlib.rcParams['figure.figsize']
fontsize = matplotlib.rcParams['font.size']
_config['dpi'] = dpi
_config['format'] = fmt
_config['width'] = width*dpi
_config['height'] = height*dpi
_config['fontsize'] = fontsize
_config['close'] = True
_config['interactive'] = matplotlib.is_interactive()
_config['angular'] = False
_config['supported_formats'] = ['png', 'jpg', 'svg']
_config['context'] = None
_config = {}
_init_config()

View file

@ -93,6 +93,15 @@
"description": "Whether display app info"
}
},
"option": {
"remote": true,
"port": -1,
"perNote": "shared",
"perUser": "scoped",
"isExistingProcess": false,
"setPermission": false,
"users": []
},
"editor": {
"language": "scala",
"editOnDblClick": false
@ -115,6 +124,15 @@
"description": "Execute multiple SQL concurrently if set true."
}
},
"option": {
"remote": true,
"port": -1,
"perNote": "shared",
"perUser": "scoped",
"isExistingProcess": false,
"setPermission": false,
"users": []
},
"editor": {
"language": "sql",
"editOnDblClick": false
@ -126,6 +144,15 @@
"className": "org.apache.zeppelin.livy.LivyPySparkInterpreter",
"properties": {
},
"option": {
"remote": true,
"port": -1,
"perNote": "shared",
"perUser": "scoped",
"isExistingProcess": false,
"setPermission": false,
"users": []
},
"editor": {
"language": "python",
"editOnDblClick": false
@ -137,6 +164,15 @@
"className": "org.apache.zeppelin.livy.LivySparkRInterpreter",
"properties": {
},
"option": {
"remote": true,
"port": -1,
"perNote": "shared",
"perUser": "scoped",
"isExistingProcess": false,
"setPermission": false,
"users": []
},
"editor": {
"language": "r",
"editOnDblClick": false

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -460,6 +460,9 @@
<fileset>
<directory>interpreter</directory>
<followSymlinks>false</followSymlinks>
<excludes>
<exclude>lib/**</exclude>
</excludes>
</fileset>
</filesets>
</configuration>

View file

@ -47,6 +47,6 @@ mvn -Dpython.test.exclude='' test -pl python -am
* JavaBuilder can't send SIGINT signal to interrupt paragraph execution. Therefore interpreter directly send a `kill SIGINT PID` to python process to interrupt execution. Python process catch SIGINT signal with some code defined in bootstrap.py
* Matplotlib display feature is made with SVG export (in string) and then displays it with html code.
* Matplotlib figures are displayed inline with the notebook automatically using a built-in backend for zeppelin in conjunction with a post-execute hook.
* `%python.sql` support for Pandas DataFrames is optional and provided using https://github.com/yhat/pandasql if user have one installed
* `%python.sql` support for Pandas DataFrames is optional and provided using https://github.com/yhat/pandasql if user have one installed

View file

@ -36,7 +36,8 @@
<py4j.version>0.9.2</py4j.version>
<python.test.exclude>
**/PythonInterpreterWithPythonInstalledTest.java,
**/PythonInterpreterPandasSqlTest.java
**/PythonInterpreterPandasSqlTest.java,
**/PythonInterpreterMatplotlibTest.java
</python.test.exclude>
</properties>

View file

@ -32,6 +32,8 @@ import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterHookRegistry.HookType;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.Scheduler;
@ -68,6 +70,12 @@ public class PythonInterpreter extends Interpreter {
@Override
public void open() {
// Add matplotlib display hook
InterpreterGroup intpGroup = getInterpreterGroup();
if (intpGroup != null && intpGroup.getInterpreterHookRegistry() != null) {
registerHook(HookType.POST_EXEC_DEV, "z._displayhook()");
}
LOG.info("Starting Python interpreter ---->");
LOG.info("Python path is set to:" + property.getProperty(ZEPPELIN_PYTHON));

View file

@ -16,10 +16,11 @@
# PYTHON 2 / 3 compatibility :
# bootstrap.py must be runnable with Python 2 or 3
# Remove interactive mode displayhook
import os
import sys
import signal
import base64
import warnings
from io import BytesIO
try:
from StringIO import StringIO
@ -117,6 +118,7 @@ class PyZeppelinContext(object):
def __init__(self):
self.max_result = 1000
self._displayhook = lambda *args: None
def input(self, name, defaultValue=""):
print(self.errorMsg)
@ -137,11 +139,14 @@ class PyZeppelinContext(object):
elif hasattr(p, '__call__'):
p() #error reporting
def show_dataframe(self, df, **kwargs):
def show_dataframe(self, df, show_index=False, **kwargs):
"""Pretty prints DF using Table Display System
"""
limit = len(df) > self.max_result
header_buf = StringIO("")
if show_index:
idx_name = str(df.index.name) if df.index.name is not None else ""
header_buf.write(idx_name + "\t")
header_buf.write(str(df.columns[0]))
for col in df.columns[1:]:
header_buf.write("\t")
@ -150,7 +155,11 @@ class PyZeppelinContext(object):
body_buf = StringIO("")
rows = df.head(self.max_result).values if limit else df.values
for row in rows:
index = df.index.values
for idx, row in zip(index, rows):
if show_index:
body_buf.write("%html <strong>{}</strong>".format(idx))
body_buf.write("\t")
body_buf.write(str(row[0]))
for cell in row[1:]:
body_buf.write("\t")
@ -164,7 +173,7 @@ class PyZeppelinContext(object):
#)
body_buf.close(); header_buf.close()
def show_matplotlib(self, p, fmt="png", width="auto", height="auto",
def show_matplotlib(self, p, fmt="png", width="auto", height="auto",
**kwargs):
"""Matplotlib show function
"""
@ -187,6 +196,39 @@ class PyZeppelinContext(object):
html = "%html <div style='width:{width};height:{height}'>{img}<div>"
print(html.format(width=width, height=height, img=img_str))
img.close()
def configure_mpl(self, **kwargs):
import mpl_config
mpl_config.configure(**kwargs)
def _setup_matplotlib(self):
# If we don't have matplotlib installed don't bother continuing
try:
import matplotlib
except ImportError:
pass
# Make sure custom backends are available in the PYTHONPATH
rootdir = os.environ.get('ZEPPELIN_HOME', os.getcwd())
mpl_path = os.path.join(rootdir, 'interpreter', 'lib', 'python')
if mpl_path not in sys.path:
sys.path.append(mpl_path)
# Finally check if backend exists, and if so configure as appropriate
try:
matplotlib.use('module://backend_zinline')
import backend_zinline
# Everything looks good so make config assuming that we are using
# an inline backend
self._displayhook = backend_zinline.displayhook
self.configure_mpl(width=600, height=400, dpi=72,
fontsize=10, interactive=True, format='png')
except ImportError:
# Fall back to Agg if no custom backend installed
matplotlib.use('Agg')
warnings.warn("Unable to load inline matplotlib backend, "
"falling back to Agg")
z = PyZeppelinContext()
z._setup_matplotlib()

View file

@ -0,0 +1,169 @@
/*
* 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.python;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.*;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterOutput;
import org.apache.zeppelin.interpreter.InterpreterOutputListener;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.Before;
import org.junit.Test;
/**
* In order for this test to work, test env must have installed:
* <ol>
* - <li>Python</li>
* - <li>Matplotlib</li>
* <ol>
*
* Your PYTHONPATH should also contain the directory of the Matplotlib
* backend files. Usually these can be found in $ZEPPELIN_HOME/interpreter/lib/python.
*
* To run manually on such environment, use:
* <code>
* mvn -Dpython.test.exclude='' test -pl python -am
* </code>
*/
public class PythonInterpreterMatplotlibTest {
private InterpreterGroup intpGroup;
private PythonInterpreter python;
private InterpreterContext context;
@Before
public void setUp() throws Exception {
Properties p = new Properties();
p.setProperty("zeppelin.python", "python");
p.setProperty("zeppelin.python.maxResult", "100");
intpGroup = new InterpreterGroup();
python = new PythonInterpreter(p);
python.setInterpreterGroup(intpGroup);
python.open();
List<Interpreter> interpreters = new LinkedList<>();
interpreters.add(python);
intpGroup.put("note", interpreters);
context = new InterpreterContext("note", "id", "title", "text", new AuthenticationInfo(),
new HashMap<String, Object>(), new GUI(),
new AngularObjectRegistry(intpGroup.getId(), null), null,
new LinkedList<InterpreterContextRunner>(), new InterpreterOutput(
new InterpreterOutputListener() {
@Override public void onAppend(InterpreterOutput out, byte[] line) {}
@Override public void onUpdate(InterpreterOutput out, byte[] output) {}
}));
}
@Test
public void dependenciesAreInstalled() {
// matplotlib
InterpreterResult ret = python.interpret("import matplotlib", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
// inline backend
ret = python.interpret("import backend_zinline", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
}
@Test
public void showPlot() {
// Simple plot test
InterpreterResult ret;
ret = python.interpret("import matplotlib.pyplot as plt", context);
ret = python.interpret("z.configure_mpl(interactive=False)", context);
ret = python.interpret("plt.plot([1, 2, 3])", context);
ret = python.interpret("plt.show()", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
assertEquals(ret.message(), Type.HTML, ret.type());
assertTrue(ret.message().contains("data:image/png;base64"));
assertTrue(ret.message().contains("<div>"));
}
@Test
// Test for when configuration is set to auto-close figures after show().
public void testClose() {
InterpreterResult ret;
InterpreterResult ret1;
InterpreterResult ret2;
ret = python.interpret("import matplotlib.pyplot as plt", context);
ret = python.interpret("z.configure_mpl(interactive=False)", context);
ret = python.interpret("plt.plot([1, 2, 3])", context);
ret1 = python.interpret("plt.show()", context);
// Second call to show() should print nothing, and Type should be TEXT.
// This is because when close=True, there should be no living instances
// of FigureManager, causing show() to return before setting the output
// type to HTML.
ret = python.interpret("plt.show()", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
assertEquals(ret.message(), Type.TEXT, ret.type());
assertTrue(ret.message().equals(""));
// Now test that new plot is drawn. It should be identical to the
// previous one.
ret = python.interpret("plt.plot([1, 2, 3])", context);
ret2 = python.interpret("plt.show()", context);
assertTrue(ret1.message().equals(ret2.message()));
}
@Test
// Test for when configuration is set to not auto-close figures after show().
public void testNoClose() {
InterpreterResult ret;
InterpreterResult ret1;
InterpreterResult ret2;
ret = python.interpret("import matplotlib.pyplot as plt", context);
ret = python.interpret("z.configure_mpl(interactive=False, close=False)", context);
ret = python.interpret("plt.plot([1, 2, 3])", context);
ret1 = python.interpret("plt.show()", context);
// Second call to show() should print nothing, and Type should be HTML.
// This is because when close=False, there should be living instances
// of FigureManager, causing show() to set the output
// type to HTML even though the figure is inactive.
ret = python.interpret("plt.show()", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
assertEquals(ret.message(), Type.HTML, ret.type());
assertTrue(ret.message().equals(""));
// Now test that plot can be reshown if it is updated. It should be
// different from the previous one because it will plot the same line
// again but in a different color.
ret = python.interpret("plt.plot([1, 2, 3])", context);
ret2 = python.interpret("plt.show()", context);
assertTrue(!ret1.message().equals(ret2.message()));
}
}

View file

@ -159,17 +159,20 @@ public class PythonInterpreterPandasSqlTest {
ret = python.interpret("import pandas as pd", context);
ret = python.interpret("import numpy as np", context);
// given a Pandas DataFrame with non-text data
// given a Pandas DataFrame with an index and non-text data
ret = python.interpret("index = pd.Index([10, 11, 12, 13], name='index_name')", context);
ret = python.interpret("d1 = {1 : [np.nan, 1, 2, 3], 'two' : [3., 4., 5., 6.7]}", context);
ret = python.interpret("df1 = pd.DataFrame(d1)", context);
ret = python.interpret("df1 = pd.DataFrame(d1, index=index)", context);
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
// when
ret = python.interpret("z.show(df1)", context);
ret = python.interpret("z.show(df1, show_index=True)", context);
// then
assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code());
assertEquals(ret.message(), Type.TABLE, ret.type());
assertTrue(ret.message().indexOf("index_name") == 0);
assertTrue(ret.message().indexOf("13") > 0);
assertTrue(ret.message().indexOf("nan") > 0);
assertTrue(ret.message().indexOf("6.7") > 0);
}

View file

@ -0,0 +1,290 @@
# /**
# * 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.
# */
import argparse
import json
from os.path import isfile
from os import getcwd
from subprocess import call, check_call
#######################################################################################################################
# I put these here so it will (hopeully) be easy(er) to bump versions / maintain
# If there is demand, we could easily make parts or all comand line arguments as well
#######################################################################################################################
tar_name = "apache-mahout-distribution-0.12.2.tar.gz"
mahout_bin_url = "http://apache.osuosl.org/mahout/0.12.2/%s" % tar_name
mahout_version = "0.12.2"
parser = argparse.ArgumentParser()
parser.add_argument("--force_download", help="force download Apache Mahout", action="store_true")
parser.add_argument("--restart_later", help="force download Apache Mahout", action="store_true")
parser.add_argument("--zeppelin_home", help="path to ZEPPELIN_HOME")
parser.add_argument("--mahout_home", help="path to MAHOUT_HOME, use this if you have already installed Apache Mahout")
parser.add_argument("--overwrite_existing", help="if %sparkMahout or %flinkMahout exist, delete them and create new ones. Otherwise Fail.", action="store_true")
args = parser.parse_args()
class ZeppelinTerpWrangler:
def __init__(self, interpreter_json_path):
self.interpreter_json_path = interpreter_json_path
def _getTerpID(self, terpName):
terp_id = None
for k, v in self.interpreter_json['interpreterSettings'].iteritems():
if v['name'] == terpName:
terp_id = k
break
return terp_id
def _terpExists(self, terpName):
terp_id = self._getTerpID(terpName)
if terp_id == None:
return False
return True
def createTerp(self, original_terp_name, new_terp_name, overwrite_existing=True ):
new_terp_id = new_terp_name
if self._terpExists(new_terp_name):
print "Found existing '%s' interpreter..." % new_terp_name
if overwrite_existing:
print "deleting %s from interpreter.json" %new_terp_name
del self.interpreter_json['interpreterSettings'][self._getTerpID(new_terp_name)]
else:
print "exiting program."
exit(1)
orig_terp_id = self._getTerpID(original_terp_name)
from copy import deepcopy
self.interpreter_json['interpreterSettings'][new_terp_id] = deepcopy(
self.interpreter_json['interpreterSettings'][orig_terp_id])
self.interpreter_json['interpreterSettings'][new_terp_id]['name'] = new_terp_name
self.interpreter_json['interpreterSettings'][new_terp_id]['id'] = new_terp_id
print "created new interpreter '%s' from interpreter '%s" % (new_terp_name, original_terp_name)
def _readTerpJson(self):
with open(self.interpreter_json_path) as f:
self.interpreter_json = json.load(f)
def _writeTerpJson(self):
with open(self.interpreter_json_path, 'wb') as f:
json.dump(self.interpreter_json, f, sort_keys=True, indent=4)
def _updateTerpProp(self, terpName, property, value):
terp_id = self._getTerpID(terpName)
self.interpreter_json['interpreterSettings'][terp_id]['properties'][property] = value
def _addTerpDep(self, terpName="", dep="", exclusions=None):
if self.interpreter_json == {}:
print "no interpreter.json loaded, reading last one downloaded"
self._readTerpJson()
terp_id = self._getTerpID(terpName)
deps = self.interpreter_json['interpreterSettings'][terp_id]['dependencies']
dep_dict = {
u'groupArtifactVersion': dep,
u'local': False
}
if exclusions != None:
dep_dict["exclusions"] = exclusions
deps.append(dep_dict)
## Remove Duplicate Dependencies
seen = set()
new_deps = list()
for d in deps:
t = d.items()
if t[0] not in seen:
seen.add(t[0])
new_deps.append(d)
self.interpreter_json['interpreterSettings'][terp_id]['dependencies'] = new_deps
def addMahoutConfig(self, terpName, mahout_home, mahout_version = "0.12.2"):
print "updating '%s' with Apache Mahout dependencies and settings" % terpName
terpDeps = ["%s/mahout-math-%s.jar" % (mahout_home, mahout_version),
"%s/mahout-math-scala_2.10-%s.jar" % (mahout_home, mahout_version)]
if "spark" in terpName.lower():
configs = {
"spark.kryo.referenceTracking": "false",
"spark.kryo.registrator": "org.apache.mahout.sparkbindings.io.MahoutKryoRegistrator",
"spark.kryoserializer.buffer": "32k",
"spark.kryoserializer.buffer.max": "600m",
"spark.serializer": "org.apache.spark.serializer.KryoSerializer"
}
terpDeps.append('%s/mahout-spark_2.10-%s-dependency-reduced.jar' % (mahout_home, mahout_version))
terpDeps.append("%s/mahout-spark_2.10-%s.jar" % (mahout_home, mahout_version))
terpDeps.append("%s/mahout-spark-shell_2.10-%s.jar" % (mahout_home, mahout_version))
if "flink" in terpName.lower():
configs = {
"taskmanager.numberOfTaskSlots" : "12"
}
addlDeps = [
"%s/mahout-flink_2.10-%s.jar" % (mahout_home, mahout_version),
"%s/mahout-hdfs-%s.jar" % (mahout_home, mahout_version),
"com.google.guava:guava:14.0.1"
#"%s/guava-14.0.1.jar" % mahout_home ## reuired in lib dir if running against cluster
]
for t in addlDeps:
terpDeps.append(t)
for k, v in configs.iteritems():
self._updateTerpProp(terpName, k, v)
for t in terpDeps:
self._addTerpDep(terpName, t)
#######################################################################################################################
# Need to be sure we know where Zeppelin Top directory is so we can edit conf files
#
#######################################################################################################################
def valid_zeppelin_home(path):
return isfile(path + "/bin/zeppelin-daemon.sh")
if args.zeppelin_home == None:
zeppelin_home = getcwd()
if (zeppelin_home.split("/")[-1] == "bin") and (isfile("zeppelin-daemon.sh")):
print "we're in the zeppelin/bin"
zeppelin_home = "/".join(zeppelin_home.split("/")[:-1])
print "--zeppelin_home not specified, using %s" % zeppelin_home
else:
zeppelin_home = args.zeppelin_home
if not valid_zeppelin_home(zeppelin_home):
print "%s does not appear to be a valid ZEPPELIN_HOME - e.g. the top level directory of the ZEPPELIN install" % zeppelin_home
exit(1)
else:
print "ZEPPELIN_HOME validated"
interpreter_json_path = zeppelin_home + "/conf/interpreter.json"
if not isfile(interpreter_json_path):
print "interpreter.json doesn't exist. Checking weather Zeppelin is running."
status = call(["bin/zeppelin-daemon.sh", 'status'], cwd=zeppelin_home)
if status == 1:
print "Zeppelin doesn't appear to be running- it is possible that Zeppelin has never been run (interpreter.json is created when Zeppelin is run)"
print "I'm going to try to start Zeppelin to create interpreter.json"
call(["bin/zeppelin-daemon.sh", 'start'], cwd=zeppelin_home)
from time import sleep
sleep(3)
else:
print "We're in the correct top-level directory, Zeppelin appears to be running, but there is no 'interpreter.json'. \
\nThis is a confusing case. Please try restarting Zeppelin, but if that doesn't work reach out on the mailing list."
if isfile(interpreter_json_path):
z = ZeppelinTerpWrangler(interpreter_json_path)
else:
print "'interpreter.json' not found in %s/conf" % args.zeppelin_home
exit(1)
#######################################################################################################################
# If --mahout_home not set, download and untar Mahout in to ZEPPELIN_HOME
# Set MAHOUT_HOME to ZEPPELIN_HOME/<mahout_untar_dir>
#######################################################################################################################
def download_mahout():
if args.force_download:
print "--force_download: OK, deleting existing tar if it exists."
call(["rm", "%s/%s" % (zeppelin_home, tar_name)])
return True
elif isfile("%s/%s" % (zeppelin_home, tar_name)):
print "%s found, skipping download" % tar_name
return False
elif args.mahout_home:
print "--mahout_home set, skipping download"
return False
else:
return True
if download_mahout():
check_call(['wget', mahout_bin_url], cwd= zeppelin_home)
check_call(['tar', 'xzf', tar_name], cwd= zeppelin_home)
if args.mahout_home:
mahout_home = args.mahout_home
else:
mahout_home = zeppelin_home + "/" + ".".join(tar_name.split(".")[:-2])
#######################################################################################################################
# Create new interpreters
#######################################################################################################################
z._readTerpJson()
z.createTerp("spark", "sparkMahout", args.overwrite_existing)
z.createTerp("flink", "flinkMahout", args.overwrite_existing)
z.addMahoutConfig("sparkMahout", mahout_home, mahout_version)
z.addMahoutConfig("flinkMahout", mahout_home, mahout_version)
z._writeTerpJson()
#######################################################################################################################
# Add "export MAHOUT_HOME=... to conf/zeppelin-env.sh
# Create if doesn't exist.
#######################################################################################################################
mahout_home_str = '\nexport MAHOUT_HOME=%s\n' % (mahout_home)
zeppelin_env_sh_path = '%s/conf/zeppelin-env.sh' % zeppelin_home
if isfile(zeppelin_env_sh_path):
with open(zeppelin_env_sh_path, 'rb') as f:
zeppelin_env_sh = f.readlines()
if any(["export MAHOUT_HOME=" in line for line in zeppelin_env_sh]):
print "'export MAHOUT_HOME=...' already exists in zeppelin_env.sh, not appending"
else:
print "appending '%s' to conf/zeppelin-env.sh" % mahout_home_str
with open(zeppelin_env_sh_path, 'a') as f:
f.write(mahout_home_str)
else:
print "appending '%s' to conf/zeppelin-env.sh" % mahout_home_str
with open(zeppelin_env_sh_path, 'wb') as f:
f.write(mahout_home_str)
#######################################################################################################################
# You have to restart Apache Zeppelin for new terps to show up... do this for user unless the specified otherwise
#
#######################################################################################################################
if not args.restart_later:
print "restarting Apache Zeppelin to load new interpreters..."
check_call(["bin/zeppelin-daemon.sh", 'restart'], cwd= zeppelin_home)
else:
print "--restart_later flag detected: remember to restart Zeppelin to see new Mahout interpreters!!"
#######################################################################################################################
# Good bye
#######################################################################################################################
print "---------------------------------------------------------------------------------------------------------------"
print "all done! Thanks for using Apache Mahout"
print "bye"

View file

@ -48,7 +48,9 @@ import org.apache.spark.sql.SQLContext;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterHookRegistry.HookType;
import org.apache.zeppelin.interpreter.LazyOpenInterpreter;
import org.apache.zeppelin.interpreter.WrappedInterpreter;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
@ -111,6 +113,11 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
@Override
public void open() {
// Add matplotlib display hook
InterpreterGroup intpGroup = getInterpreterGroup();
if (intpGroup != null && intpGroup.getInterpreterHookRegistry() != null) {
registerHook(HookType.POST_EXEC_DEV, "z._displayhook()");
}
DepInterpreter depInterpreter = getDepInterpreter();
// load libraries from Dependency Interpreter

View file

@ -15,7 +15,7 @@
# limitations under the License.
#
import sys, getopt, traceback, json, re
import os, sys, getopt, traceback, json, re
from py4j.java_gateway import java_import, JavaGateway, GatewayClient
from py4j.protocol import Py4JJavaError
@ -50,6 +50,7 @@ class Logger(object):
class PyZeppelinContext(dict):
def __init__(self, zc):
self.z = zc
self._displayhook = lambda *args: None
def show(self, obj):
from pyspark.sql import DataFrame
@ -116,6 +117,39 @@ class PyZeppelinContext(dict):
return self.z.getHook(event)
return self.z.getHook(event, replName)
def _setup_matplotlib(self):
# If we don't have matplotlib installed don't bother continuing
try:
import matplotlib
except ImportError:
return
# Make sure custom backends are available in the PYTHONPATH
rootdir = os.environ.get('ZEPPELIN_HOME', os.getcwd())
mpl_path = os.path.join(rootdir, 'interpreter', 'lib', 'python')
if mpl_path not in sys.path:
sys.path.append(mpl_path)
# Finally check if backend exists, and if so configure as appropriate
try:
matplotlib.use('module://backend_zinline')
import backend_zinline
# Everything looks good so make config assuming that we are using
# an inline backend
self._displayhook = backend_zinline.displayhook
self.configure_mpl(width=600, height=400, dpi=72, fontsize=10,
interactive=True, format='png', context=self.z)
except ImportError:
# Fall back to Agg if no custom backend installed
matplotlib.use('Agg')
warnings.warn("Unable to load inline matplotlib backend, "
"falling back to Agg")
def configure_mpl(self, **kwargs):
import mpl_config
mpl_config.configure(**kwargs)
def __tupleToScalaTuple2(self, tuple):
if (len(tuple) == 2):
return gateway.jvm.scala.Tuple2(tuple[0], tuple[1])
@ -244,6 +278,7 @@ sqlContext = sqlc
completion = PySparkCompletion(intp)
z = PyZeppelinContext(intp.getZeppelinContext())
z._setup_matplotlib()
while True :
req = intp.getStatements()
@ -251,6 +286,22 @@ while True :
stmts = req.statements().split("\n")
jobGroup = req.jobGroup()
final_code = []
# Get post-execute hooks
try:
global_hook = intp.getHook('post_exec_dev')
except:
global_hook = None
try:
user_hook = z.getHook('post_exec')
except:
user_hook = None
nhooks = 0
for hook in (global_hook, user_hook):
if hook:
nhooks += 1
for s in stmts:
if s == None:
@ -268,7 +319,9 @@ while True :
# so that the last statement's evaluation will be printed to stdout
sc.setJobGroup(jobGroup, "Zeppelin")
code = compile('\n'.join(final_code), '<stdin>', 'exec', ast.PyCF_ONLY_AST, 1)
to_run_exec, to_run_single = code.body[:-1], code.body[-1:]
to_run_hooks = code.body[-nhooks:]
to_run_exec, to_run_single = (code.body[:-(nhooks + 1)],
[code.body[-(nhooks + 1)]])
try:
for node in to_run_exec:
@ -280,6 +333,11 @@ while True :
mod = ast.Interactive([node])
code = compile(mod, '<stdin>', 'single')
exec(code)
for node in to_run_hooks:
mod = ast.Module([node])
code = compile(mod, '<stdin>', 'exec')
exec(code)
except:
raise Exception(traceback.format_exc())

View file

@ -29,7 +29,7 @@
<dependencySets>
<dependencySet>
<!-- Enable access to all projects in the current multimodule build!
<!-- Enable access to all projects in the current multimodule build!
<useAllReactorProjects>true</useAllReactorProjects> -->
<!-- Now, select which projects to include in this module-set. -->
<includes>
@ -92,4 +92,3 @@
</fileSets>-->
</assembly>

View file

@ -163,6 +163,8 @@ The following components are provided under Apache License.
(Apache 2.0) tez-runtime-internals (org.apache.tez:tez-runtime-internals:0.7.0 - http://tez.apache.org)
(Apache 2.0) tez-mapreduce (org.apache.tez:tez-mapreduce:0.7.0 - http://tez.apache.org)
(Apache 2.0) tez-yarn-timeline-history-with-acls (org.apache.tez:tez-yarn-timeline-history-with-acls:0.7.0 - http://tez.apache.org)
(Apache 2.0) jna (net.java.dev.jna:jna:4.1.0 https://github.com/java-native-access/jna)
(Apache 2.0) MathJax v2.7.0 - https://github.com/mathjax/MathJax/blob/2.7.0/LICENSE
========================================================================
MIT licenses
@ -208,7 +210,8 @@ The following components are provided under the MIT License.
(The MIT License) JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.16 - http://www.slf4j.org)
(The MIT License) angular-resource (angular-resource - https://github.com/angular/angular.js/tree/master/src/ngResource)
(The MIT License) minimal-json (com.eclipsesource.minimal-json:minimal-json:0.9.4 - https://github.com/ralfstx/minimal-json)
(The MIT License) pyrolite (net.razorvine:pyrolite:4.9) - https://github.com/irmen/Pyrolite/blob/v4.9/LICENSE
(The MIT License) pyrolite (net.razorvine:pyrolite:4.9) - https://github.com/irmen/Pyrolite/blob/v4.9/LICENSE)
(The MIT License) libpam4j (org.kohsuke:libpam4j:1.8 https://github.com/kohsuke/libpam4j/blob/master/src/main/java/org/jvnet/libpam/PAM.java)
========================================================================
BSD-style licenses

View file

@ -319,6 +319,7 @@ public abstract class Interpreter {
private Map<String, InterpreterProperty> properties;
private Map<String, Object> editor;
private String path;
private InterpreterOption option;
public RegisteredInterpreter(String name, String group, String className,
Map<String, InterpreterProperty> properties) {
@ -376,6 +377,9 @@ public abstract class Interpreter {
return getGroup() + "." + getName();
}
public InterpreterOption getOption() {
return option;
}
}
/**

View file

@ -17,8 +17,6 @@
package org.apache.zeppelin.interpreter;
import com.google.common.base.Preconditions;
import java.util.List;
/**
@ -77,9 +75,12 @@ public class InterpreterOption {
}
public InterpreterOption(boolean remote, String perUser, String perNote) {
Preconditions.checkNotNull(remote);
Preconditions.checkNotNull(perUser);
Preconditions.checkNotNull(perNote);
if (perUser == null) {
throw new NullPointerException("perUser can not be null.");
}
if (perNote == null) {
throw new NullPointerException("perNote can not be null.");
}
this.remote = remote;
this.perUser = perUser;

View file

@ -213,11 +213,17 @@ public class InterpreterOutput extends OutputStream {
return out.toByteArray();
}
private boolean typeShouldBeDetected() {
return getType() == InterpreterResult.Type.TABLE ? false : true;
}
public void flush() throws IOException {
synchronized (outList) {
buffer.flush();
byte[] bytes = buffer.toByteArray();
bytes = detectTypeFromLine(bytes);
if (typeShouldBeDetected()) {
bytes = detectTypeFromLine(bytes);
}
if (bytes != null) {
outList.add(bytes);
if (type == InterpreterResult.Type.TEXT) {

View file

@ -124,14 +124,20 @@ public class RemoteInterpreterManagedProcess extends RemoteInterpreterProcess
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < getConnectTimeout()) {
if (RemoteInterpreterUtils.checkIfRemoteEndpointAccessible("localhost", port)) {
break;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
logger.error("Exception in RemoteInterpreterProcess while synchronized reference " +
"Thread.sleep", e);
try {
if (RemoteInterpreterUtils.checkIfRemoteEndpointAccessible("localhost", port)) {
break;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
logger.error("Exception in RemoteInterpreterProcess while synchronized reference " +
"Thread.sleep", e);
}
}
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Remote interpreter not yet accessible at localhost:" + port);
}
}
}

View file

@ -21,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
@ -46,9 +47,19 @@ public class RemoteInterpreterUtils {
discover.connect(new InetSocketAddress(host, port), 1000);
discover.close();
return true;
} catch (IOException e) {
} catch (ConnectException cne) {
// end point is not accessible
LOGGER.debug(e.getMessage(), e);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Remote endpoint '" + host + ":" + port + "' is not accessible " +
"(might be initializing): " + cne.getMessage());
}
return false;
} catch (IOException ioe) {
// end point is not accessible
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Remote endpoint '" + host + ":" + port + "' is not accessible " +
"(might be initializing): " + ioe.getMessage());
}
return false;
}
}

View file

@ -115,6 +115,13 @@ public class InterpreterOutputTest implements InterpreterOutputListener {
assertEquals(InterpreterResult.Type.HTML, out.getType());
}
@Test
public void testMagicData() throws IOException {
out.write("%table col1\tcol2\n%html <h3> This is a hack </h3>\t234\n".getBytes());
assertEquals(InterpreterResult.Type.TABLE, out.getType());
assertEquals("col1\tcol2\n%html <h3> This is a hack </h3>\t234\n", new String(out.toByteArray()));
}
@Override
public void onAppend(InterpreterOutput out, byte[] line) {
numAppendEvent++;

View file

@ -319,6 +319,10 @@
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
</exclusion>
<exclusion>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</exclusion>
</exclusions>
</dependency>
@ -353,6 +357,22 @@
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>libpam4j</artifactId>
<version>1.8</version>
<exclusions>
<exclusion>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
<build>

View file

@ -0,0 +1,91 @@
/*
* 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.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.crypto.hash.HashService;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.jvnet.libpam.PAM;
import org.jvnet.libpam.PAMException;
import org.jvnet.libpam.UnixUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* An {@code AuthorizingRealm} base on libpam4j.
*/
public class PamRealm extends AuthorizingRealm {
private static final Logger LOG = LoggerFactory.getLogger(ZeppelinHubRealm.class);
private String service;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String> roles = new LinkedHashSet<>();
UserPrincipal user = principals.oneByType(UserPrincipal.class);
if (user != null){
roles.addAll(user.getUnixUser().getGroups());
}
return new SimpleAuthorizationInfo(roles);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
UnixUser user;
try {
user = (new PAM(this.getService()))
.authenticate(userToken.getUsername(), new String(userToken.getPassword()));
} catch (PAMException e) {
throw new AuthenticationException("Authentication failed for PAM.", e);
}
return new SimpleAuthenticationInfo(
new UserPrincipal(user),
userToken.getCredentials(),
getName());
}
public String getService() {
return service;
}
public void setService(String service) {
this.service = service;
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.realm;
import org.jvnet.libpam.UnixUser;
import java.security.Principal;
/**
* A {@code java.security.Principal} implememtation for use with Shiro {@code PamRealm}
*/
public class UserPrincipal implements Principal {
private final UnixUser userName;
public UserPrincipal(UnixUser userName) {
this.userName = userName;
}
@Override
public String getName() {
return userName.getUserName();
}
public UnixUser getUnixUser() {
return userName;
}
@Override
public String toString() {
return String.valueOf(userName.getUserName());
}
}

View file

@ -42,6 +42,7 @@ import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.rest.exception.BadRequestException;
import org.apache.zeppelin.rest.exception.NotFoundException;
import org.apache.zeppelin.rest.exception.ForbiddenException;
import org.apache.zeppelin.rest.message.CronRequest;
@ -451,6 +452,38 @@ public class NotebookRestApi {
return new JsonResponse<>(Status.OK, "", p).build();
}
@PUT
@Path("{noteId}/paragraph/{paragraphId}/config")
@ZeppelinApi
public Response updateParagraphConfig(@PathParam("noteId") String noteId,
@PathParam("paragraphId") String paragraphId, String message) throws IOException {
String user = SecurityUtils.getPrincipal();
LOG.info("{} will update paragraph config {} {}", user, noteId, paragraphId);
Note note = notebook.getNote(noteId);
checkIfNoteIsNotNull(note);
checkIfUserCanWrite(noteId, "Insufficient privileges you cannot update this paragraph config");
Paragraph p = note.getParagraph(paragraphId);
checkIfParagraphIsNotNull(p);
Map<String, Object> newConfig = gson.fromJson(message, HashMap.class);
if (newConfig == null || newConfig.isEmpty()) {
LOG.warn("{} is trying to update paragraph {} of note {} with empty config",
user, paragraphId, noteId);
throw new BadRequestException("paragraph config cannot be empty");
}
Map<String, Object> origConfig = p.getConfig();
for (String key : newConfig.keySet()) {
origConfig.put(key, newConfig.get(key));
}
p.setConfig(origConfig);
AuthenticationInfo subject = new AuthenticationInfo(user);
note.persist(subject);
return new JsonResponse<>(Status.OK, "", p).build();
}
/**
* Move paragraph REST API
*

View file

@ -0,0 +1,47 @@
/*
* 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.rest.exception;
import org.apache.zeppelin.utils.ExceptionUtils;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
/**
* BadRequestException handler for WebApplicationException.
*/
public class BadRequestException extends WebApplicationException {
public BadRequestException() {
super(ExceptionUtils.jsonResponse(BAD_REQUEST));
}
private static Response badRequestJson(String message) {
return ExceptionUtils.jsonResponseContent(BAD_REQUEST, message);
}
public BadRequestException(Throwable cause, String message) {
super(cause, badRequestJson(message));
}
public BadRequestException(String message) {
super(badRequestJson(message));
}
}

View file

@ -0,0 +1,66 @@
/*
* 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.realm;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* The test will only be executed if the environment variables PAM_USER and PAM_PASS are present. They should
* contain username and password of an valid system user to make the test pass. The service needs to be configured
* under /etc/pam.d/sshd to resolve and authenticate the system user.
*
* Contains main() function so the test can be executed manually.
*
* Set in MacOS to run in IDE(A):
* $ launchctl setenv PAM_USER user
* $ launchctl setenv PAM_PASS xxxxx
*/
public class PamRealmTest {
@Test
public void testDoGetAuthenticationInfo() {
PamRealm realm = new PamRealm();
realm.setService("sshd");
String pam_user = System.getenv("PAM_USER");
String pam_pass = System.getenv("PAM_PASS");
assumeTrue(pam_user != null);
assumeTrue(pam_pass != null);
// mock shiro auth token
UsernamePasswordToken authToken = mock(UsernamePasswordToken.class);
when(authToken.getUsername()).thenReturn(pam_user);
when(authToken.getPassword()).thenReturn(pam_pass.toCharArray());
when(authToken.getCredentials()).thenReturn(pam_pass);
AuthenticationInfo authInfo = realm.doGetAuthenticationInfo(authToken);
assertTrue(authInfo.getCredentials() != null);
}
public static void main(String[] args) {
PamRealmTest test = new PamRealmTest();
test.testDoGetAuthenticationInfo();
}
}

View file

@ -94,7 +94,7 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
Note note2 = ZeppelinServer.notebook.createNote(anonymous);
// Set only writers
jsonRequest = "{\"readers\":[],\"owners\":[]," +
"\"writers\":[\"admin-team\"]}";
"\"writers\":[\"admin-team\"]}";
put = httpPut("/notebook/" + note2.getId() + "/permissions/", jsonRequest);
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
@ -175,7 +175,32 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
//cleanup
ZeppelinServer.notebook.removeNote(note1.getId(), anonymous);
ZeppelinServer.notebook.removeNote(clonedNoteId, anonymous);
}
@Test
public void testUpdateParagraphConfig() throws IOException {
Note note = ZeppelinServer.notebook.createNote(anonymous);
String noteId = note.getId();
Paragraph p = note.addParagraph();
assertNull(p.getConfig().get("colWidth"));
String paragraphId = p.getId();
String jsonRequest = "{\"colWidth\": 6.0}";
PutMethod put = httpPut("/notebook/" + noteId + "/paragraph/" + paragraphId +"/config", jsonRequest);
assertThat("test testUpdateParagraphConfig:", put, isAllowed());
Map<String, Object> resp = gson.fromJson(put.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, Object> respBody = (Map<String, Object>) resp.get("body");
Map<String, Object> config = (Map<String, Object>) respBody.get("config");
put.releaseConnection();
assertEquals(config.get("colWidth"), 6.0);
note = ZeppelinServer.notebook.getNote(noteId);
assertEquals(note.getParagraph(paragraphId).getConfig().get("colWidth"), 6.0);
//cleanup
ZeppelinServer.notebook.removeNote(noteId, anonymous);
}
@Test

View file

@ -14,9 +14,11 @@
"nv": false,
"ace": false,
"d3": false,
"MathJax": false,
"BootstrapDialog": false,
"Handsontable": false,
"moment": false
"moment": false,
"zeppelin" : false
},
"rules": {
"no-bitwise": 2,

View file

@ -466,6 +466,12 @@ module.exports = function(grunt) {
cwd: 'bower_components/jquery-ui/themes/base/images',
src: '{,*/}*.{png,jpg,jpeg,gif}',
dest: '<%= yeoman.dist %>/styles/images'
}, {
expand: true,
cwd: 'bower_components/MathJax',
src: [
'extensions/**', 'jax/**', 'fonts/**'],
dest: '<%= yeoman.dist %>'
}]
},
styles: {

View file

@ -33,7 +33,8 @@
"handsontable": "~0.24.2",
"moment-duration-format": "^1.3.0",
"select2": "^4.0.3",
"github-markdown-css": "^2.4.0"
"github-markdown-css": "^2.4.0",
"MathJax": "2.7.0"
},
"devDependencies": {
"angular-mocks": "1.5.0"

View file

@ -19,39 +19,10 @@ limitations under the License.
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('table')}"
ng-click="setGraphMode('table', true)"
tooltip="Table" tooltip-placement="bottom"><i class="fa fa-table"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('multiBarChart')}"
ng-click="setGraphMode('multiBarChart', true)"
tooltip="Bar Chart" tooltip-placement="bottom"><i class="fa fa-bar-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('pieChart')}"
ng-click="setGraphMode('pieChart', true)"
tooltip="Pie Chart" tooltip-placement="bottom"><i class="fa fa-pie-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('stackedAreaChart')}"
ng-click="setGraphMode('stackedAreaChart', true)"
tooltip="Area Chart" tooltip-placement="bottom"><i class="fa fa-area-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('lineChart') || isGraphMode('lineWithFocusChart')}"
ng-click="paragraph.config.graph.lineWithFocus ? setGraphMode('lineWithFocusChart', true) : setGraphMode('lineChart', true)"
tooltip="Line Chart" tooltip-placement="bottom"><i class="fa fa-line-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('scatterChart')}"
ng-click="setGraphMode('scatterChart', true)"
tooltip="Scatter Chart" tooltip-placement="bottom"><i class="cf cf-scatter-chart"></i>
ng-repeat="viz in builtInTableDataVisualizationList track by $index"
ng-class="{'active' : isGraphMode(viz.id)}"
ng-click="setGraphMode(viz.id, true, false)"
tooltip="{{viz.name}}" tooltip-placement="bottom"><i ng-class="viz.icon"></i>
</button>
<button type="button"

View file

@ -14,41 +14,11 @@ limitations under the License.
<div id="p{{paragraph.id}}_graph"
class="graphContainer"
ng-class="{'noOverflow': getGraphMode()=='table'}"
ng-if="getResultType()=='TABLE'"
ng-show="getResultType()=='TABLE'"
>
<div ng-if="getGraphMode()=='table'"
id="p{{paragraph.id}}_table"
class="table">
</div>
<div ng-if="getGraphMode()=='multiBarChart'"
id="p{{paragraph.id}}_multiBarChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='pieChart'"
id="p{{paragraph.id}}_pieChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='stackedAreaChart'"
id="p{{paragraph.id}}_stackedAreaChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='lineChart'"
id="p{{paragraph.id}}_lineChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='lineWithFocusChart'"
id="p{{paragraph.id}}_lineWithFocusChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='scatterChart'"
id="p{{paragraph.id}}_scatterChart">
<svg></svg>
<div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
id="p{{paragraph.id}}_{{viz.id}}"
ng-show="getGraphMode()==viz.id">
</div>
</div>

View file

@ -24,7 +24,7 @@ limitations under the License.
<label>
<input type="checkbox"
ng-model="paragraph.config.graph.lineWithFocus"
ng-click="toggleLineWithFocus()" />
ng-click="onGraphOptionChange()" />
show line chart with focus
</label>
</div>

View file

@ -20,11 +20,11 @@ limitations under the License.
All fields:
<div class="allFields row">
<ul class="noDot">
<li class="liVertical" ng-repeat="col in paragraph.result.columnNames">
<li class="liVertical" ng-repeat="col in tableDataColumns">
<div class="btn btn-default btn-xs"
data-drag="true"
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
ng-model="paragraph.result.columnNames"
ng-model="tableDataColumns"
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
</div>

View file

@ -0,0 +1,165 @@
/*
* 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.
*/
'use strict';
var zeppelin = zeppelin || {};
/**
* pivot table data and return d3 chart data
*/
zeppelin.PivotTransformation = function(config) {
zeppelin.Transformation.call(this, config);
};
zeppelin.PivotTransformation.prototype = Object.create(zeppelin.Transformation.prototype);
/**
* Method will be invoked when tableData or config changes
*/
zeppelin.PivotTransformation.prototype.transform = function(tableData) {
return this.pivot(
tableData,
this.config.keys,
this.config.groups,
this.config.values);
};
zeppelin.PivotTransformation.prototype.pivot = function(data, keys, groups, values) {
var aggrFunc = {
sum: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
},
count: function(a, b) {
var varA = (a !== undefined) ? parseInt(a) : 0;
var varB = (b !== undefined) ? 1 : 0;
return varA + varB;
},
min: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.min(varA,varB);
},
max: function(a, b) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return Math.max(varA,varB);
},
avg: function(a, b, c) {
var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
return varA + varB;
}
};
var aggrFuncDiv = {
sum: false,
count: false,
min: false,
max: false,
avg: true
};
var schema = {};
var rows = {};
for (var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
var s = schema;
var p = rows;
for (var k = 0; k < keys.length; k++) {
var key = keys[k];
// add key to schema
if (!s[key.name]) {
s[key.name] = {
order: k,
index: key.index,
type: 'key',
children: {}
};
}
s = s[key.name].children;
// add key to row
var keyKey = row[key.index];
if (!p[keyKey]) {
p[keyKey] = {};
}
p = p[keyKey];
}
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
var groupKey = row[group.index];
// add group to schema
if (!s[groupKey]) {
s[groupKey] = {
order: g,
index: group.index,
type: 'group',
children: {}
};
}
s = s[groupKey].children;
// add key to row
if (!p[groupKey]) {
p[groupKey] = {};
}
p = p[groupKey];
}
for (var v = 0; v < values.length; v++) {
var value = values[v];
var valueKey = value.name + '(' + value.aggr + ')';
// add value to schema
if (!s[valueKey]) {
s[valueKey] = {
type: 'value',
order: v,
index: value.index
};
}
// add value to row
if (!p[valueKey]) {
p[valueKey] = {
value: (value.aggr !== 'count') ? row[value.index] : 1,
count: 1
};
} else {
p[valueKey] = {
value: aggrFunc[value.aggr](p[valueKey].value, row[value.index], p[valueKey].count + 1),
count: (aggrFuncDiv[value.aggr]) ? p[valueKey].count + 1 : p[valueKey].count
};
}
}
}
//console.log('schema=%o, rows=%o', schema, rows);
return {
keys: keys,
groups: groups,
values: values,
schema: schema,
rows: rows
};
};

View file

@ -0,0 +1,90 @@
/*
* 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.
*/
'use strict';
var zeppelin = zeppelin || {};
/**
* Create table data object from paragraph table type result
*/
zeppelin.TableData = function(columns, rows, comment) {
this.columns = columns || [];
this.rows = rows || [];
this.comment = comment || '';
};
zeppelin.TableData.prototype.loadParagraphResult = function(paragraphResult) {
if (!paragraphResult || paragraphResult.type !== 'TABLE') {
console.log('Can not load paragraph result');
return;
}
var columnNames = [];
var rows = [];
var array = [];
var textRows = paragraphResult.msg.split('\n');
var comment = '';
var commentRow = false;
for (var i = 0; i < textRows.length; i++) {
var textRow = textRows[i];
if (commentRow) {
comment += textRow;
continue;
}
if (textRow === '') {
if (rows.length > 0) {
commentRow = true;
}
continue;
}
var textCols = textRow.split('\t');
var cols = [];
var cols2 = [];
for (var j = 0; j < textCols.length; j++) {
var col = textCols[j];
if (i === 0) {
columnNames.push({name: col, index: j, aggr: 'sum'});
} else {
var parsedCol = this.parseTableCell(col);
cols.push(parsedCol);
cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: parsedCol});
}
}
if (i !== 0) {
rows.push(cols);
array.push(cols2);
}
}
this.comment = comment;
this.columns = columnNames;
this.rows = rows;
};
zeppelin.TableData.prototype.parseTableCell = function(cell) {
if (!isNaN(cell)) {
if (cell.length === 0 || Number(cell) > Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) {
return cell;
} else {
return Number(cell);
}
}
var d = moment(cell);
if (d.isValid()) {
return d;
}
return cell;
};

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
'use strict';
var zeppelin = zeppelin || {};
/**
* Base class for visualization
*/
zeppelin.Transformation = function(config) {
this.config = config;
};
/**
* Method will be invoked when tableData or config changes
*/
zeppelin.Transformation.prototype.transform = function(tableData) {
// override this
};
zeppelin.Transformation.prototype.setConfig = function(config) {
this.config = config;
};

View file

@ -0,0 +1,68 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in area chart
*/
zeppelin.AreachartVisualization = function(targetEl, config) {
zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
var PivotTransformation = zeppelin.PivotTransformation;
this.pivot = new PivotTransformation(config);
this.xLables = [];
};
zeppelin.AreachartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
zeppelin.AreachartVisualization.prototype.type = function() {
return 'stackedAreaChart';
};
zeppelin.AreachartVisualization.prototype.getTransformation = function() {
return this.pivot;
};
zeppelin.AreachartVisualization.prototype.render = function(tableData) {
var pivot = this.pivot.transform(tableData);
var d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
pivot.groups,
pivot.values,
false,
true,
false);
this.xLabels = d3Data.xLabels;
zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
};
/**
* Set new config
*/
zeppelin.AreachartVisualization.prototype.setConfig = function(config) {
zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
this.pivot.setConfig(config);
};
zeppelin.AreachartVisualization.prototype.configureChart = function(chart) {
var self = this;
chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
chart.yAxisTickFormat(function(d) {return self.yAxisTickFormat(d);});
chart.yAxis.axisLabelDistance(50);
chart.useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
};

View file

@ -0,0 +1,64 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in bar char
*/
zeppelin.BarchartVisualization = function(targetEl, config) {
zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
var PivotTransformation = zeppelin.PivotTransformation;
this.pivot = new PivotTransformation(config);
};
zeppelin.BarchartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
zeppelin.BarchartVisualization.prototype.type = function() {
return 'multiBarChart';
};
zeppelin.BarchartVisualization.prototype.getTransformation = function() {
return this.pivot;
};
zeppelin.BarchartVisualization.prototype.render = function(tableData) {
var pivot = this.pivot.transform(tableData);
var d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
pivot.groups,
pivot.values,
true,
false,
true);
zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
};
/**
* Set new config
*/
zeppelin.BarchartVisualization.prototype.setConfig = function(config) {
zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
this.pivot.setConfig(config);
};
zeppelin.BarchartVisualization.prototype.configureChart = function(chart) {
var self = this;
chart.yAxis.axisLabelDistance(50);
chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d);});
};

View file

@ -0,0 +1,86 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in line chart
*/
zeppelin.LinechartVisualization = function(targetEl, config) {
zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
var PivotTransformation = zeppelin.PivotTransformation;
this.pivot = new PivotTransformation(config);
this.xLables = [];
};
zeppelin.LinechartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
zeppelin.LinechartVisualization.prototype.type = function() {
if (this.config.lineWithFocus) {
return 'lineWithFocusChart';
} else {
return 'lineChart';
}
};
zeppelin.LinechartVisualization.prototype.getTransformation = function() {
return this.pivot;
};
zeppelin.LinechartVisualization.prototype.render = function(tableData) {
this.tableData = tableData;
var pivot = this.pivot.transform(tableData);
var d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
pivot.groups,
pivot.values,
false,
true,
false);
this.xLabels = d3Data.xLabels;
zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
};
/**
* Set new config
*/
zeppelin.LinechartVisualization.prototype.setConfig = function(config) {
zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
this.pivot.setConfig(config);
// change mode
if (this.currentMode !== config.lineWithFocus) {
zeppelin.Nvd3ChartVisualization.prototype.destroy.call(this);
this.currentMode = config.lineWithFocus;
}
};
zeppelin.LinechartVisualization.prototype.configureChart = function(chart) {
var self = this;
chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d, self.xLabels);});
chart.yAxis.axisLabelDistance(50);
if (chart.useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline
chart.useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
}
if (this.config.forceY) {
chart.forceY([0]); // force y-axis minimum to 0 for line chart.
} else {
chart.forceY([]);
}
};

View file

@ -0,0 +1,257 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in table format
*/
zeppelin.Nvd3ChartVisualization = function(targetEl, config) {
zeppelin.Visualization.call(this, targetEl, config);
this.targetEl.append('<svg></svg>');
};
zeppelin.Nvd3ChartVisualization.prototype = Object.create(zeppelin.Visualization.prototype);
zeppelin.Visualization.prototype.refresh = function() {
if (this.chart) {
this.chart.update();
}
};
zeppelin.Nvd3ChartVisualization.prototype.render = function(data) {
var type = this.type();
var d3g = data.d3g;
if (!this.chart) {
this.chart = nv.models[type]();
}
this.configureChart(this.chart);
var animationDuration = 300;
var numberOfDataThreshold = 150;
var height = this.targetEl.height();
// turn off animation when dataset is too large. (for performance issue)
// still, since dataset is large, the chart content sequentially appears like animated.
try {
if (d3g[0].values.length > numberOfDataThreshold) {
animationDuration = 0;
}
} catch (ignoreErr) {
}
d3.select('#' + this.targetEl[0].id + ' svg')
.attr('height', height)
.datum(d3g)
.transition()
.duration(animationDuration)
.call(this.chart);
d3.select('#' + this.targetEl[0].id + ' svg').style.height = height + 'px';
};
zeppelin.Nvd3ChartVisualization.prototype.type = function() {
// override this and return chart type
};
zeppelin.Nvd3ChartVisualization.prototype.configureChart = function(chart) {
// override this to configure chart
};
zeppelin.Nvd3ChartVisualization.prototype.groupedThousandsWith3DigitsFormatter = function(x) {
return d3.format(',')(d3.round(x, 3));
};
zeppelin.Nvd3ChartVisualization.prototype.customAbbrevFormatter = function(x) {
var s = d3.format('.3s')(x);
switch (s[s.length - 1]) {
case 'G': return s.slice(0, -1) + 'B';
}
return s;
};
zeppelin.Nvd3ChartVisualization.prototype.xAxisTickFormat = function(d, xLabels) {
if (xLabels[d] && (isNaN(parseFloat(xLabels[d])) || !isFinite(xLabels[d]))) { // to handle string type xlabel
return xLabels[d];
} else {
return d;
}
};
zeppelin.Nvd3ChartVisualization.prototype.yAxisTickFormat = function(d) {
if (Math.abs(d) >= Math.pow(10,6)) {
return this.customAbbrevFormatter(d);
}
return this.groupedThousandsWith3DigitsFormatter(d);
};
zeppelin.Nvd3ChartVisualization.prototype.d3DataFromPivot = function(
schema, rows, keys, groups, values, allowTextXAxis, fillMissingValues, multiBarChart) {
// construct table data
var d3g = [];
var concat = function(o, n) {
if (!o) {
return n;
} else {
return o + '.' + n;
}
};
var getSchemaUnderKey = function(key, s) {
for (var c in key.children) {
s[c] = {};
getSchemaUnderKey(key.children[c], s[c]);
}
};
var traverse = function(sKey, s, rKey, r, func, rowName, rowValue, colName) {
//console.log("TRAVERSE sKey=%o, s=%o, rKey=%o, r=%o, rowName=%o, rowValue=%o, colName=%o", sKey, s, rKey, r, rowName, rowValue, colName);
if (s.type === 'key') {
rowName = concat(rowName, sKey);
rowValue = concat(rowValue, rKey);
} else if (s.type === 'group') {
colName = concat(colName, rKey);
} else if (s.type === 'value' && sKey === rKey || valueOnly) {
colName = concat(colName, rKey);
func(rowName, rowValue, colName, r);
}
for (var c in s.children) {
if (fillMissingValues && s.children[c].type === 'group' && r[c] === undefined) {
var cs = {};
getSchemaUnderKey(s.children[c], cs);
traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName);
continue;
}
for (var j in r) {
if (s.children[c].type === 'key' || c === j) {
traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName);
}
}
}
};
var valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0);
var noKey = (keys.length === 0);
var isMultiBarChart = multiBarChart;
var sKey = Object.keys(schema)[0];
var rowNameIndex = {};
var rowIdx = 0;
var colNameIndex = {};
var colIdx = 0;
var rowIndexValue = {};
for (var k in rows) {
traverse(sKey, schema[sKey], k, rows[k], function(rowName, rowValue, colName, value) {
//console.log("RowName=%o, row=%o, col=%o, value=%o", rowName, rowValue, colName, value);
if (rowNameIndex[rowValue] === undefined) {
rowIndexValue[rowIdx] = rowValue;
rowNameIndex[rowValue] = rowIdx++;
}
if (colNameIndex[colName] === undefined) {
colNameIndex[colName] = colIdx++;
}
var i = colNameIndex[colName];
if (noKey && isMultiBarChart) {
i = 0;
}
if (!d3g[i]) {
d3g[i] = {
values: [],
key: (noKey && isMultiBarChart) ? 'values' : colName
};
}
var xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue);
var yVar = 0;
if (xVar === undefined) { xVar = colName; }
if (value !== undefined) {
yVar = isNaN(value.value) ? 0 : parseFloat(value.value) / parseFloat(value.count);
}
d3g[i].values.push({
x: xVar,
y: yVar
});
});
}
// clear aggregation name, if possible
var namesWithoutAggr = {};
var colName;
var withoutAggr;
// TODO - This part could use som refactoring - Weird if/else with similar actions and variable names
for (colName in colNameIndex) {
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (!namesWithoutAggr[withoutAggr]) {
namesWithoutAggr[withoutAggr] = 1;
} else {
namesWithoutAggr[withoutAggr]++;
}
}
if (valueOnly) {
for (var valueIndex = 0; valueIndex < d3g[0].values.length; valueIndex++) {
colName = d3g[0].values[valueIndex].x;
if (!colName) {
continue;
}
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[0].values[valueIndex].x = withoutAggr;
}
}
} else {
for (var d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[d3gIndex].key = withoutAggr;
}
}
// use group name instead of group.value as a column name, if there're only one group and one value selected.
if (groups.length === 1 && values.length === 1) {
for (d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
colName = colName.split('.').slice(0, -1).join('.');
d3g[d3gIndex].key = colName;
}
}
}
return {
xLabels: rowIndexValue,
d3g: d3g
};
};
/**
* method will be invoked when visualization need to be destroyed.
* Don't need to destroy this.targetEl.
*/
zeppelin.Visualization.prototype.destroy = function() {
if (this.chart) {
d3.selectAll('#' + this.targetEl[0].id + ' svg > *').remove();
this.chart = undefined;
}
};

View file

@ -0,0 +1,73 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in pie chart
*/
zeppelin.PiechartVisualization = function(targetEl, config) {
zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
var PivotTransformation = zeppelin.PivotTransformation;
this.pivot = new PivotTransformation(config);
};
zeppelin.PiechartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
zeppelin.PiechartVisualization.prototype.type = function() {
return 'pieChart';
};
zeppelin.PiechartVisualization.prototype.getTransformation = function() {
return this.pivot;
};
zeppelin.PiechartVisualization.prototype.render = function(tableData) {
var pivot = this.pivot.transform(tableData);
var d3Data = this.d3DataFromPivot(
pivot.schema,
pivot.rows,
pivot.keys,
pivot.groups,
pivot.values,
true,
false,
false);
var d = d3Data.d3g;
var d3g = [];
if (d.length > 0) {
for (var i = 0; i < d[0].values.length ; i++) {
var e = d[0].values[i];
d3g.push({
label: e.x,
value: e.y
});
}
}
zeppelin.Nvd3ChartVisualization.prototype.render.call(this, {d3g: d3g});
};
/**
* Set new config
*/
zeppelin.PiechartVisualization.prototype.setConfig = function(config) {
zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
this.pivot.setConfig(config);
};
zeppelin.PiechartVisualization.prototype.configureChart = function(chart) {
chart.x(function(d) { return d.label;}).y(function(d) { return d.value;});
};

View file

@ -0,0 +1,295 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in scatter char
*/
zeppelin.ScatterchartVisualization = function(targetEl, config) {
zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
};
zeppelin.ScatterchartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
zeppelin.ScatterchartVisualization.prototype.type = function() {
return 'scatterChart';
};
zeppelin.ScatterchartVisualization.prototype.getTransformation = function() {
return this.pivot;
};
zeppelin.ScatterchartVisualization.prototype.render = function(tableData) {
this.tableData = tableData;
var d3Data = this.setScatterChart(tableData, true);
this.xLabels = d3Data.xLabels;
this.yLabels = d3Data.yLabels;
zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
};
zeppelin.ScatterchartVisualization.prototype.configureChart = function(chart) {
var self = this;
chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d, self.yLabels);});
// configure how the tooltip looks.
chart.tooltipContent(function(key, x, y, graph, data) {
var tooltipContent = '<h3>' + key + '</h3>';
if (self.config.scatter.size &&
self.isValidSizeOption(self.config.scatter, self.tableData.rows)) {
tooltipContent += '<p>' + data.point.size + '</p>';
}
return tooltipContent;
});
chart.showDistX(true).showDistY(true);
//handle the problem of tooltip not showing when muliple points have same value.
};
zeppelin.ScatterchartVisualization.prototype.setScatterChart = function(data, refresh) {
var xAxis = this.config.scatter.xAxis;
var yAxis = this.config.scatter.yAxis;
var group = this.config.scatter.group;
var size = this.config.scatter.size;
var xValues = [];
var yValues = [];
var rows = {};
var d3g = [];
var rowNameIndex = {};
var colNameIndex = {};
var grpNameIndex = {};
var rowIndexValue = {};
var colIndexValue = {};
var grpIndexValue = {};
var rowIdx = 0;
var colIdx = 0;
var grpIdx = 0;
var grpName = '';
var xValue;
var yValue;
var row;
if (!xAxis && !yAxis) {
return {
d3g: []
};
}
for (var i = 0; i < data.rows.length; i++) {
row = data.rows[i];
if (xAxis) {
xValue = row[xAxis.index];
xValues[i] = xValue;
}
if (yAxis) {
yValue = row[yAxis.index];
yValues[i] = yValue;
}
}
var isAllDiscrete = ((xAxis && yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
(!xAxis && this.isDiscrete(yValues)) ||
(!yAxis && this.isDiscrete(xValues)));
if (isAllDiscrete) {
rows = this.setDiscreteScatterData(data);
} else {
rows = data.rows;
}
if (!group && isAllDiscrete) {
grpName = 'count';
} else if (!group && !size) {
if (xAxis && yAxis) {
grpName = '(' + xAxis.name + ', ' + yAxis.name + ')';
} else if (xAxis && !yAxis) {
grpName = xAxis.name;
} else if (!xAxis && yAxis) {
grpName = yAxis.name;
}
} else if (!group && size) {
grpName = size.name;
}
for (i = 0; i < rows.length; i++) {
row = rows[i];
if (xAxis) {
xValue = row[xAxis.index];
}
if (yAxis) {
yValue = row[yAxis.index];
}
if (group) {
grpName = row[group.index];
}
var sz = (isAllDiscrete) ? row[row.length - 1] : ((size) ? row[size.index] : 1);
if (grpNameIndex[grpName] === undefined) {
grpIndexValue[grpIdx] = grpName;
grpNameIndex[grpName] = grpIdx++;
}
if (xAxis && rowNameIndex[xValue] === undefined) {
rowIndexValue[rowIdx] = xValue;
rowNameIndex[xValue] = rowIdx++;
}
if (yAxis && colNameIndex[yValue] === undefined) {
colIndexValue[colIdx] = yValue;
colNameIndex[yValue] = colIdx++;
}
if (!d3g[grpNameIndex[grpName]]) {
d3g[grpNameIndex[grpName]] = {
key: grpName,
values: []
};
}
d3g[grpNameIndex[grpName]].values.push({
x: xAxis ? (isNaN(xValue) ? rowNameIndex[xValue] : parseFloat(xValue)) : 0,
y: yAxis ? (isNaN(yValue) ? colNameIndex[yValue] : parseFloat(yValue)) : 0,
size: isNaN(parseFloat(sz)) ? 1 : parseFloat(sz)
});
}
return {
xLabels: rowIndexValue,
yLabels: colIndexValue,
d3g: d3g
};
};
zeppelin.ScatterchartVisualization.prototype.setDiscreteScatterData = function(data) {
var xAxis = this.config.scatter.xAxis;
var yAxis = this.config.scatter.yAxis;
var group = this.config.scatter.group;
var xValue;
var yValue;
var grp;
var rows = {};
for (var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
if (xAxis) {
xValue = row[xAxis.index];
}
if (yAxis) {
yValue = row[yAxis.index];
}
if (group) {
grp = row[group.index];
}
var key = xValue + ',' + yValue + ',' + grp;
if (!rows[key]) {
rows[key] = {
x: xValue,
y: yValue,
group: grp,
size: 1
};
} else {
rows[key].size++;
}
}
// change object into array
var newRows = [];
for (var r in rows) {
var newRow = [];
if (xAxis) { newRow[xAxis.index] = rows[r].x; }
if (yAxis) { newRow[yAxis.index] = rows[r].y; }
if (group) { newRow[group.index] = rows[r].group; }
newRow[data.rows[0].length] = rows[r].size;
newRows.push(newRow);
}
return newRows;
};
zeppelin.ScatterchartVisualization.prototype.isDiscrete = function(field) {
var getUnique = function(f) {
var uniqObj = {};
var uniqArr = [];
var j = 0;
for (var i = 0; i < f.length; i++) {
var item = f[i];
if (uniqObj[item] !== 1) {
uniqObj[item] = 1;
uniqArr[j++] = item;
}
}
return uniqArr;
};
for (var i = 0; i < field.length; i++) {
if (isNaN(parseFloat(field[i])) &&
(typeof field[i] === 'string' || field[i] instanceof String)) {
return true;
}
}
var threshold = 0.05;
var unique = getUnique(field);
if (unique.length / field.length < threshold) {
return true;
} else {
return false;
}
};
zeppelin.ScatterchartVisualization.prototype.isValidSizeOption = function(options) {
var xValues = [];
var yValues = [];
var rows = this.tableData.rows;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var size = row[options.size.index];
//check if the field is numeric
if (isNaN(parseFloat(size)) || !isFinite(size)) {
return false;
}
if (options.xAxis) {
var x = row[options.xAxis.index];
xValues[i] = x;
}
if (options.yAxis) {
var y = row[options.yAxis.index];
yValues[i] = y;
}
}
//check if all existing fields are discrete
var isAllDiscrete = ((options.xAxis && options.yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
(!options.xAxis && this.isDiscrete(yValues)) ||
(!options.yAxis && this.isDiscrete(xValues)));
if (isAllDiscrete) {
return false;
}
return true;
};

View file

@ -0,0 +1,81 @@
/*
* 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.
*/
'use strict';
/**
* Visualize data in table format
*/
zeppelin.TableVisualization = function(targetEl) {
zeppelin.Visualization.call(this, targetEl);
console.log('Init table viz');
targetEl.addClass('table');
};
zeppelin.TableVisualization.prototype = Object.create(zeppelin.Visualization.prototype);
zeppelin.TableVisualization.prototype.refresh = function() {
this.hot.render();
};
zeppelin.TableVisualization.prototype.render = function(tableData) {
var height = this.targetEl.height();
var container = this.targetEl.css('height', height).get(0);
var resultRows = tableData.rows;
var columnNames = _.pluck(tableData.columns, 'name');
if (this.hot) {
this.hot.destroy();
}
this.hot = new Handsontable(container, {
colHeaders: columnNames,
data: resultRows,
rowHeaders: false,
stretchH: 'all',
sortIndicator: true,
columnSorting: true,
contextMenu: false,
manualColumnResize: true,
manualRowResize: true,
readOnly: true,
readOnlyCellClassName: '', // don't apply any special class so we can retain current styling
fillHandle: false,
fragmentSelection: true,
disableVisualSelection: true,
cells: function(row, col, prop) {
var cellProperties = {};
cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) {
if (value instanceof moment) {
td.innerHTML = value._i;
} else if (!isNaN(value)) {
cellProperties.format = '0,0.[00000]';
td.style.textAlign = 'left';
Handsontable.renderers.NumericRenderer.apply(this, arguments);
} else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) {
td.innerHTML = value.substring('%html'.length);
} else {
Handsontable.renderers.TextRenderer.apply(this, arguments);
}
};
return cellProperties;
}
});
};
zeppelin.TableVisualization.prototype.destroy = function() {
if (this.hot) {
this.hot.destroy();
}
};

View file

@ -0,0 +1,103 @@
/*
* 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.
*/
'use strict';
var zeppelin = zeppelin || {};
/**
* Base class for visualization
*/
zeppelin.Visualization = function(targetEl, config) {
this.targetEl = targetEl;
this.config = config;
this._resized = false;
this._active = false;
};
/**
* get transformation
*/
zeppelin.Visualization.prototype.getTransformation = function() {
// override this
};
/**
* Method will be invoked when data or configuration changed
*/
zeppelin.Visualization.prototype.render = function(tableData) {
// override this
};
/**
* Refresh visualization.
*/
zeppelin.Visualization.prototype.refresh = function() {
// override this
};
/**
* Activate. invoked when visualization is selected
*/
zeppelin.Visualization.prototype.activate = function() {
console.log('active');
if (!this._active && this._resized) {
var self = this;
// give some time for element ready
setTimeout(function() {self.refresh();}, 300);
this._resized = false;
}
this._active = true;
};
/**
* Activate. invoked when visualization is de selected
*/
zeppelin.Visualization.prototype.deactivate = function() {
console.log('deactive');
this._active = false;
};
/**
* Is active
*/
zeppelin.Visualization.prototype.isActive = function() {
return this._active;
};
/**
* When window or paragraph is resized
*/
zeppelin.Visualization.prototype.resize = function() {
if (this.isActive()) {
this.refresh();
} else {
this._resized = true;
}
};
/**
* Set new config
*/
zeppelin.Visualization.prototype.setConfig = function(config) {
this.config = config;
};
/**
* method will be invoked when visualization need to be destroyed.
* Don't need to destroy this.targetEl.
*/
zeppelin.Visualization.prototype.destroy = function() {
// override this
};

View file

@ -100,6 +100,20 @@ limitations under the License.
<![endif]-->
<!-- endbuild -->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
extensions: ["tex2jax.js"],
jax: ["input/TeX", "output/HTML-CSS"],
tex2jax: {
inlineMath: [ ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true
},
"HTML-CSS": { availableFonts: ["TeX"] },
messageStyle: "none"
});
</script>
<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
@ -147,12 +161,24 @@ limitations under the License.
<script src="bower_components/handsontable/dist/handsontable.js"></script>
<script src="bower_components/moment-duration-format/lib/moment-duration-format.js"></script>
<script src="bower_components/select2/dist/js/select2.js"></script>
<script src="bower_components/MathJax/MathJax.js"></script>
<!-- endbower -->
<!-- endbuild -->
<!-- build:js({.tmp,src}) scripts/scripts.js -->
<script src="app/app.js"></script>
<script src="app/app.controller.js"></script>
<script src="app/home/home.controller.js"></script>
<script src="app/tabledata/tabledata.js"></script>
<script src="app/tabledata/transformation.js"></script>
<script src="app/tabledata/pivot.js"></script>
<script src="app/visualization/visualization.js"></script>
<script src="app/visualization/builtins/visualization-table.js"></script>
<script src="app/visualization/builtins/visualization-nvd3chart.js"></script>
<script src="app/visualization/builtins/visualization-barchart.js"></script>
<script src="app/visualization/builtins/visualization-piechart.js"></script>
<script src="app/visualization/builtins/visualization-areachart.js"></script>
<script src="app/visualization/builtins/visualization-linechart.js"></script>
<script src="app/visualization/builtins/visualization-scatterchart.js"></script>
<script src="app/notebook/notebook.controller.js"></script>
<script src="app/jobmanager/jobmanager.controller.js"></script>
<script src="app/jobmanager/jobs/job.controller.js"></script>

View file

@ -30,7 +30,8 @@
"inject": false,
"it": false,
"jasmine": false,
"spyOn": false
"spyOn": false,
"zeppelin" : false
}
}

View file

@ -65,17 +65,21 @@ module.exports = function(config) {
'bower_components/handsontable/dist/handsontable.js',
'bower_components/moment-duration-format/lib/moment-duration-format.js',
'bower_components/select2/dist/js/select2.js',
'bower_components/MathJax/MathJax.js',
'bower_components/angular-mocks/angular-mocks.js',
// endbower
'src/app/app.js',
'src/app/app.controller.js',
'src/app/tabledata/transformation.js',
'src/app/**/*.js',
'src/components/**/*.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
exclude: [],
exclude: [
'src/app/visualization/builtins/*.js'
],
// web server port
port: 9002,

View file

@ -36,7 +36,7 @@ describe('Controller: ParagraphCtrl', function() {
'closeTable', 'openTable', 'showTitle', 'hideTitle', 'setTitle', 'showLineNumbers', 'hideLineNumbers',
'changeColWidth', 'columnWidthClass', 'toggleGraphOption', 'toggleOutput', 'loadForm',
'aceChanged', 'aceLoaded', 'getEditorValue', 'getProgress', 'getExecutionTime', 'isResultOutdated',
'getResultType', 'loadTableData', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange',
'getResultType', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange',
'removeGraphOptionKeys', 'removeGraphOptionValues', 'removeGraphOptionGroups', 'setGraphOptionValueAggr',
'removeScatterOptionXaxis', 'removeScatterOptionYaxis', 'removeScatterOptionGroup',
'removeScatterOptionSize'];
@ -64,10 +64,8 @@ describe('Controller: ParagraphCtrl', function() {
scope.getResultType = jasmine.createSpy('getResultType spy').andCallFake(function() {
return 'TABLE';
});
spyOn(scope, 'loadTableData');
spyOn(scope, 'setGraphMode');
scope.init(paragraphMock);
expect(scope.loadTableData).toHaveBeenCalled();
expect(scope.setGraphMode).toHaveBeenCalled();
expect(scope.getGraphMode()).toEqual('table');
});

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
'use strict';
describe('TableData build', function() {
var td;
beforeEach(function() {
var TableData = zeppelin.TableData;
td = new TableData();
});
it('should initialize the default value', function() {
expect(td.columns.length).toBe(0);
expect(td.rows.length).toBe(0);
expect(td.comment).toBe('');
});
it('should able to create Tabledata from paragraph result', function() {
td.loadParagraphResult({
type: 'TABLE',
msg: 'key\tvalue\na\t10\nb\t20\n\nhello'
});
expect(td.columns.length).toBe(2);
expect(td.rows.length).toBe(2);
expect(td.comment).toBe('hello');
});
});

View file

@ -238,7 +238,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
interpreterInfo =
new InterpreterInfo(r.getClassName(), r.getName(), r.isDefaultInterpreter(),
r.getEditor());
add(r.getGroup(), interpreterInfo, r.getProperties(), r.getPath());
add(r.getGroup(), interpreterInfo, r.getProperties(), defaultOption, r.getPath());
}
for (String settingId : interpreterSettingsRef.keySet()) {
@ -350,9 +350,11 @@ public class InterpreterFactory implements InterpreterGroupFactory {
InterpreterInfo interpreterInfo =
new InterpreterInfo(registeredInterpreter.getClassName(), registeredInterpreter.getName(),
registeredInterpreter.isDefaultInterpreter(), registeredInterpreter.getEditor());
// use defaultOption if it is not specified in interpreter-setting.json
InterpreterOption option = registeredInterpreter.getOption() == null ? defaultOption :
registeredInterpreter.getOption();
add(registeredInterpreter.getGroup(), interpreterInfo, registeredInterpreter.getProperties(),
absolutePath);
option, absolutePath);
}
}
@ -617,12 +619,11 @@ public class InterpreterFactory implements InterpreterGroupFactory {
}
private InterpreterSetting add(String group, InterpreterInfo interpreterInfo,
Map<String, InterpreterProperty> interpreterProperties, String path)
Map<String, InterpreterProperty> interpreterProperties, InterpreterOption option, String path)
throws InterpreterException, IOException, RepositoryException {
ArrayList<InterpreterInfo> infos = new ArrayList<>();
infos.add(interpreterInfo);
return add(group, infos, new ArrayList<Dependency>(), defaultOption,
interpreterProperties, path);
return add(group, infos, new ArrayList<Dependency>(), option, interpreterProperties, path);
}
/**

View file

@ -138,7 +138,7 @@ public class ZeppelinhubClient {
Session session = future.get();
zeppelinSession = ZeppelinhubSession.createInstance(session, zeppelinhubToken);
} catch (IOException | InterruptedException | ExecutionException e) {
LOG.info("Couldnt connect to zeppelinhub", e);
LOG.info("Couldnt connect to zeppelinhub - {}", e.toString());
zeppelinSession = ZeppelinhubSession.EMPTY;
}
return zeppelinSession;

View file

@ -54,7 +54,7 @@ public class ZeppelinWebsocket implements WebSocketListener {
@Override
public void onWebSocketError(Throwable e) {
LOG.warn("Zeppelin socket connection error ", e);
LOG.warn("Zeppelin socket connection error: {}", e.toString());
}
@Override
@ -67,7 +67,7 @@ public class ZeppelinWebsocket implements WebSocketListener {
zeppelinClient.handleMsgFromZeppelin(data, noteId);
}
} catch (Exception e) {
LOG.error("Failed to send message to ZeppelinHub: ", e);
LOG.error("Failed to send message to ZeppelinHub: {}", e.toString());
}
}

View file

@ -58,7 +58,7 @@ public class ZeppelinhubWebsocket implements WebSocketListener {
@Override
public void onWebSocketError(Throwable cause) {
LOG.error("Got error", cause);
LOG.error("Remote websocket error");
}
@Override