Merge branch 'master' into ZEPPELIN-1306

This commit is contained in:
astroshim 2016-09-27 11:51:05 +09:00
commit 5c4b32a464
54 changed files with 1434 additions and 431 deletions

View file

@ -22,6 +22,9 @@
"defaultValue": "100000",
"description": "Maximum number of rows to fetch from BigQuery"
}
},
"editor": {
"language": "sql"
}
}
]

View file

@ -161,6 +161,8 @@ function upstart() {
# where the service manager starts and stops the process
initialize_default_directories
echo "ZEPPELIN_CLASSPATH: ${ZEPPELIN_CLASSPATH_OVERRIDES}:${CLASSPATH}" >> "${ZEPPELIN_OUTFILE}"
$ZEPPELIN_RUNNER $JAVA_OPTS -cp $ZEPPELIN_CLASSPATH_OVERRIDES:$CLASSPATH $ZEPPELIN_MAIN >> "${ZEPPELIN_OUTFILE}"
}
@ -177,6 +179,8 @@ function start() {
initialize_default_directories
echo "ZEPPELIN_CLASSPATH: ${ZEPPELIN_CLASSPATH_OVERRIDES}:${CLASSPATH}" >> "${ZEPPELIN_OUTFILE}"
nohup nice -n $ZEPPELIN_NICENESS $ZEPPELIN_RUNNER $JAVA_OPTS -cp $ZEPPELIN_CLASSPATH_OVERRIDES:$CLASSPATH $ZEPPELIN_MAIN >> "${ZEPPELIN_OUTFILE}" 2>&1 < /dev/null &
pid=$!
if [[ -z "${pid}" ]]; then
@ -254,6 +258,7 @@ case "${1}" in
start
;;
restart)
echo "${ZEPPELIN_NAME} is restarting" >> "${ZEPPELIN_OUTFILE}"
stop
start
;;

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -48,7 +48,7 @@ There are three locations where you can store your interpreter group, name and o
{ZEPPELIN_INTERPRETER_DIR}/{YOUR_OWN_INTERPRETER_DIR}/interpreter-setting.json
```
Here is an example of `interpreter-setting.json` on your own interpreter.
Here is an example of `interpreter-setting.json` on your own interpreter. Note that if you don't specify editor object, your interpreter will use plain text mode for syntax highlighting.
```json
[
@ -69,6 +69,9 @@ Here is an example of `interpreter-setting.json` on your own interpreter.
"defaultValue": "property2DefaultValue",
"description": "Property 2 description"
}, ...
},
"editor": {
"language": "your-syntax-highlight-language"
}
},
{
@ -81,8 +84,8 @@ Finally, Zeppelin uses static initialization with the following:
```
static {
Interpreter.register("MyInterpreterName", MyClassName.class.getName());
}
Interpreter.register("MyInterpreterName", MyClassName.class.getName());
}
```
**Static initialization is deprecated and will be supported until 0.6.0.**
@ -96,15 +99,20 @@ some interpreter specific code...
```
## Programming Languages for Interpreter
If the interpreter uses a specific programming language ( like Scala, Python, SQL ), it is generally recommended to add a syntax highlighting supported for that to the notebook paragraph editor.
If the interpreter uses a specific programming language (like Scala, Python, SQL), it is generally recommended to add a syntax highlighting supported for that to the notebook paragraph editor.
To check out the list of languages supported, see the `mode-*.js` files under `zeppelin-web/bower_components/ace-builds/src-noconflict` or from [github.com/ajaxorg/ace-builds](https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict).
If you want to add a new set of syntax highlighting,
1. Add the `mode-*.js` file to <code>[zeppelin-web/bower.json](https://github.com/apache/zeppelin/blob/master/zeppelin-web/bower.json)</code> ( when built, <code>[zeppelin-web/src/index.html](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/index.html)</code> will be changed automatically. ).
2. Add to the list of `editorMode` in <code>[zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js)</code> - it follows the pattern 'ace/mode/x' where x is the name.
3. Add to the code that checks for `%` prefix and calls `session.setMode(editorMode.x)` in `setParagraphMode` located in <code>[zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js)</code>.
2. Add `editor` object to `interpreter-setting.json` file. If you want to set your language to `java` for example, add:
```
"editor": {
"language": "java"
}
```
## Install your interpreter binary

View file

@ -1,7 +1,7 @@
---
layout: page
title: "Quick Start"
description: "This page will help you to get started and guide you through installation of Apache Zeppelin, running it in the command line and basic configuration options."
description: "This page will help you get started and will guide you through installing Apache Zeppelin, running it in the command line and configuring options."
group: install
---
<!--
@ -20,14 +20,14 @@ limitations under the License.
{% include JB/setup %}
# Quick Start
Welcome to your first trial to explore Apache Zeppelin!
This page will help you to get started and here is the list of topics covered.
<div id="toc"></div>
Welcome to Apache Zeppelin! On this page are instructions to help you get started.
## Installation
Apache Zeppelin officially supports and is tested on next environments.
Apache Zeppelin officially supports and is tested on the following environments:
<table class="table-configuration">
<tr>
@ -44,21 +44,22 @@ Apache Zeppelin officially supports and is tested on next environments.
</tr>
</table>
There are two options to install Apache Zeppelin on your machine. One is [downloading pre-built binary package](#downloading-binary-package) from the archive.
You can download not only the latest stable version but also the older one if you need.
The other option is [building from the source](#building-from-source).
Although it can be unstable somehow since it is on development status, you can explore newly added feature and change it as you want.
To install Apache Zeppelin, you have two options:
* You can [download pre-built binary packages](#downloading-binary-package) from the archive. This is usually easier than building from source, and you can download the latest stable version (or older versions, if necessary).
* You can also [build from source](#building-from-source). This gives you a development version of Zeppelin, which is more unstable but has new features.
### Downloading Binary Package
If you want to install Apache Zeppelin with a stable binary package, please visit [Apache Zeppelin download Page](http://zeppelin.apache.org/download.html).
Stable binary packages are available on the [Apache Zeppelin Download Page](http://zeppelin.apache.org/download.html). You can download a default package with all interpreters, or you can download the *net-install* package, which lets you choose which interpreters to install.
If you have downloaded `netinst` binary, [install additional interpreters](../manual/interpreterinstallation.html) before you start Zeppelin. Or simply run `./bin/install-interpreter.sh --all`.
If you downloaded the default package, just unpack it in a directory of your choice and you're ready to go. If you downloaded the *net-install* package, you should manually [install additional interpreters](../manual/interpreterinstallation.html) first. You can also install everything by running `./bin/install-interpreter.sh --all`.
After unpacking, jump to [Starting Apache Zeppelin with Command Line](#starting-apache-zeppelin-with-command-line) section.
After unpacking, jump to the [Starting Apache Zeppelin with Command Line](#starting-apache-zeppelin-with-command-line).
### Building from Source
If you want to build from the source, the software below needs to be installed on your system.
If you want to build from source, you must first install the following dependencies:
<table class="table-configuration">
<tr>
@ -67,7 +68,7 @@ If you want to build from the source, the software below needs to be installed o
</tr>
<tr>
<td>Git</td>
<td></td>
<td>(Any Version)</td>
</tr>
<tr>
<td>Maven</td>
@ -75,22 +76,23 @@ If you want to build from the source, the software below needs to be installed o
</tr>
</table>
If you don't have it installed yet, please check [Before Build](https://github.com/apache/zeppelin/blob/master/README.md#before-build) section and follow step by step instructions from there.
If you haven't installed Git and Maven yet, check the [Before Build](https://github.com/apache/zeppelin/blob/master/README.md#before-build) section and follow the step by step instructions from there.
####1. Clone Apache Zeppelin repository
####1. Clone the Apache Zeppelin repository
```
git clone https://github.com/apache/zeppelin.git
```
####2. Build source with options
Each interpreters requires different build options. For the further information about options, please see [Build](https://github.com/apache/zeppelin#build) section.
Each interpreter requires different build options. For more information about build options, please see the [Build](https://github.com/apache/zeppelin#build) section.
```
mvn clean package -DskipTests [Options]
```
Here are some examples with several options
Here are some examples with several options:
```
# build with spark-2.0, scala-2.11
@ -110,24 +112,26 @@ mvn clean package -Pspark-1.5 -Dhadoop.version=2.6.0-cdh5.5.0 -Phadoop-2.6 -Pven
mvn clean package -Pspark-1.5 -Pmapr50 -DskipTests
```
For the further information about building with source, please see [README.md](https://github.com/apache/zeppelin/blob/master/README.md) in Zeppelin repository.
For further information about building from source, please see [README.md](https://github.com/apache/zeppelin/blob/master/README.md) in the Zeppelin repository.
## Starting Apache Zeppelin with Command Line
#### Start Zeppelin
## Starting Apache Zeppelin from the Command Line
#### Starting Apache Zeppelin
On all platforms except for Windows:
```
bin/zeppelin-daemon.sh start
```
If you are using Windows
If you are using Windows:
```
bin\zeppelin.cmd
```
After successful start, visit [http://localhost:8080](http://localhost:8080) with your web browser.
After Zeppelin has started successfully, go to [http://localhost:8080](http://localhost:8080) with your web browser.
#### Stop Zeppelin
#### Stopping Zeppelin
```
bin/zeppelin-daemon.sh stop
@ -137,10 +141,10 @@ bin/zeppelin-daemon.sh stop
> **Note :** The below description was written based on Ubuntu Linux.
Apache Zeppelin can be auto started as a service with an init script, such as services managed by **upstart**.
Apache Zeppelin can be auto-started as a service with an init script, using a service manager like **upstart**.
The following is an example of upstart script to be saved as `/etc/init/zeppelin.conf`
This also allows the service to be managed with commands such as
This is an example upstart script saved as `/etc/init/zeppelin.conf`
This allows the service to be managed with commands such as
```
sudo service zeppelin start
@ -174,24 +178,25 @@ chdir /usr/share/zeppelin
exec bin/zeppelin-daemon.sh upstart
```
## What is the next?
Congratulation on your successful Apache Zeppelin installation! Here are two next steps you might need.
## Next Steps:
#### If you are new to Apache Zeppelin
* For an in-depth overview of Apache Zeppelin UI, head to [Explore Apache Zeppelin UI](../quickstart/explorezeppelinui.html).
* After getting familiar with Apache Zeppelin UI, have fun with a short walk-through [Tutorial](../quickstart/tutorial.html) that uses Apache Spark backend.
* If you need more configuration setting for Apache Zeppelin, jump to the next section: [Apache Zeppelin Configuration](#apache-zeppelin-configuration).
Congratulations, you have successfully installed Apache Zeppelin! Here are two next steps you might find useful:
#### If you are new to Apache Zeppelin...
* For an in-depth overview of the Apache Zeppelin UI, head to [Explore Apache Zeppelin UI](../quickstart/explorezeppelinui.html).
* After getting familiar with the Apache Zeppelin UI, have fun with a short walk-through [Tutorial](../quickstart/tutorial.html) that uses the Apache Spark backend.
* If you need more configuration for Apache Zeppelin, jump to the next section: [Apache Zeppelin Configuration](#apache-zeppelin-configuration).
#### If you need more information about Spark or JDBC interpreter setting
* Apache Zeppelin provides deep integration with [Apache Spark](http://spark.apache.org/). For the further informtation, see [Spark Interpreter for Apache Zeppelin](../interpreter/spark.html).
* Also, you can use generic JDBC connections in Apache Zeppelin. Go to [Generic JDBC Interpreter for Apache Zeppelin](../interpreter/jdbc.html).
#### If you need more information about Spark or JDBC interpreter settings...
* Apache Zeppelin provides deep integration with [Apache Spark](http://spark.apache.org/). For more informtation, see [Spark Interpreter for Apache Zeppelin](../interpreter/spark.html).
* You can also use generic JDBC connections in Apache Zeppelin. Go to [Generic JDBC Interpreter for Apache Zeppelin](../interpreter/jdbc.html).
#### If you are in multi-user environment
* You can set permissions for your notebooks and secure data resource in multi-user environment. Go to **More** -> **Security** section.
#### If you are in a multi-user environment...
* You can set permissions for your notebooks and secure data resource in a multi-user environment. Go to **More** -> **Security** section.
## Apache Zeppelin Configuration
You can configure Apache Zeppelin with both **environment variables** in `conf/zeppelin-env.sh` (`conf\zeppelin-env.cmd` for Windows) and **Java properties** in `conf/zeppelin-site.xml`. If both are defined, then the **environment variables** will take priority.
You can configure Apache Zeppelin with either **environment variables** in `conf/zeppelin-env.sh` (`conf\zeppelin-env.cmd` for Windows) or **Java properties** in `conf/zeppelin-site.xml`. If both are defined, then the **environment variables** will take priority.
<table class="table-configuration">
<tr>
@ -228,19 +233,19 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
<td>ZEPPELIN_ALLOWED_ORIGINS</td>
<td>zeppelin.server.allowed.origins</td>
<td>*</td>
<td>Enables a way to specify a ',' separated list of allowed origins for rest and websockets. <br /> i.e. http://localhost:8080 </td>
<td>Enables a way to specify a ',' separated list of allowed origins for REST and websockets. <br /> i.e. http://localhost:8080 </td>
</tr>
<tr>
<td>N/A</td>
<td>zeppelin.anonymous.allowed</td>
<td>true</td>
<td>Anonymous user is allowed by default.</td>
<td>The anonymous user is allowed by default.</td>
</tr>
<tr>
<td>ZEPPELIN_SERVER_CONTEXT_PATH</td>
<td>zeppelin.server.context.path</td>
<td>/</td>
<td>A context path of the web application</td>
<td>Context path of the web application</td>
</tr>
<tr>
<td>ZEPPELIN_SSL</td>
@ -300,19 +305,19 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
<td>ZEPPELIN_NOTEBOOK_HOMESCREEN</td>
<td>zeppelin.notebook.homescreen</td>
<td></td>
<td>A notebook id displayed in Apache Zeppelin homescreen <br />i.e. 2A94M5J1Z</td>
<td>Display notebook IDs on the Apache Zeppelin homescreen <br />i.e. 2A94M5J1Z</td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE</td>
<td>zeppelin.notebook.homescreen.hide</td>
<td>false</td>
<td>This value can be "true" when to hide the notebook id set by <code>ZEPPELIN_NOTEBOOK_HOMESCREEN</code> on the Apache Zeppelin homescreen. <br />For the further information, please read <a href="../manual/notebookashomepage.html">Customize your Zeppelin homepage</a>.</td>
<td>Hide the notebook ID set by <code>ZEPPELIN_NOTEBOOK_HOMESCREEN</code> on the Apache Zeppelin homescreen. <br />For the further information, please read <a href="../manual/notebookashomepage.html">Customize your Zeppelin homepage</a>.</td>
</tr>
<tr>
<td>ZEPPELIN_WAR_TEMPDIR</td>
<td>zeppelin.war.tempdir</td>
<td>webapps</td>
<td>A location of jetty temporary directory</td>
<td>Location of the jetty temporary directory</td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_DIR</td>
@ -330,7 +335,7 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
<td>ZEPPELIN_NOTEBOOK_S3_USER</td>
<td>zeppelin.notebook.s3.user</td>
<td>user</td>
<td>A user name of S3 bucket<br />i.e. <code>bucket/user/notebook/2A94M5J1Z/note.json</code></td>
<td>User name of an S3 bucket<br />i.e. <code>bucket/user/notebook/2A94M5J1Z/note.json</code></td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_S3_ENDPOINT</td>
@ -360,25 +365,25 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
<td>ZEPPELIN_NOTEBOOK_AZURE_SHARE</td>
<td>zeppelin.notebook.azure.share</td>
<td>zeppelin</td>
<td>Share where the notebook files will be saved</td>
<td>Azure Share where the notebook files will be saved</td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_AZURE_USER</td>
<td>zeppelin.notebook.azure.user</td>
<td>user</td>
<td>An optional user name of Azure file share<br />i.e. <code>share/user/notebook/2A94M5J1Z/note.json</code></td>
<td>Optional user name of an Azure file share<br />i.e. <code>share/user/notebook/2A94M5J1Z/note.json</code></td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_STORAGE</td>
<td>zeppelin.notebook.storage</td>
<td>org.apache.zeppelin.notebook.repo.VFSNotebookRepo</td>
<td>Comma separated list of notebook storage</td>
<td>Comma separated list of notebook storage locations</td>
</tr>
<tr>
<td>ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC</td>
<td>zeppelin.notebook.one.way.sync</td>
<td>false</td>
<td>If there are multiple notebook storages, should we treat the first one as the only source of truth?</td>
<td>If there are multiple notebook storage locations, should we treat the first one as the only source of truth?</td>
</tr>
<tr>
<td>ZEPPELIN_INTERPRETERS</td>
@ -389,7 +394,7 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
</td>
<td>
Comma separated interpreter configurations [Class] <br/>
<span style="font-style:italic">NOTE: This property is deprecated since Zeppelin-0.6.0 and will not be supported from Zeppelin-0.7.0</span>
<span style="font-style:italic">NOTE: This property is deprecated since Zeppelin-0.6.0 and will not be supported from Zeppelin-0.7.0 on.</span>
</td>
</tr>
<tr>
@ -402,6 +407,6 @@ You can configure Apache Zeppelin with both **environment variables** in `conf/z
<td>ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE</td>
<td>zeppelin.websocket.max.text.message.size</td>
<td>1024000</td>
<td>Size in characters of the maximum text message to be received by websocket.</td>
<td>Size (in characters) of the maximum text message that can be received by websocket.</td>
</tr>
</table>

View file

@ -25,14 +25,42 @@ limitations under the License.
## Overview
[Markdown](http://daringfireball.net/projects/markdown/) is a plain text formatting syntax designed so that it can be converted to HTML.
Apache Zeppelin uses markdown4j. For more examples and extension support, please checkout [here](https://code.google.com/p/markdown4j/).
Apache Zeppelin uses [markdown4j](https://github.com/jdcasey/markdown4j) and [pegdown](https://github.com/sirthias/pegdown) as markdown parsers.
In Zeppelin notebook, you can use ` %md ` in the beginning of a paragraph to invoke the Markdown interpreter and generate static html from Markdown plain text.
In Zeppelin, Markdown interpreter is enabled by default.
In Zeppelin, Markdown interpreter is enabled by default and uses the [markdown4j](https://github.com/jdcasey/markdown4j) parser.
<img src="../assets/themes/zeppelin/img/docs-img/markdown-interpreter-setting.png" width="60%" />
## Configuration
<table class="table-configuration">
<tr>
<th>Name</th>
<th>Default Value</th>
<th>Description</th>
</tr>
<tr>
<td>markdown.parser.type</td>
<td>markdown4j</td>
<td>Markdown Parser Type. <br/> Available values: markdown4j, pegdown.</td>
</tr>
</table>
## Example
The following example demonstrates the basic usage of Markdown in a Zeppelin notebook.
<img src="../assets/themes/zeppelin/img/docs-img/markdown-example.png" width="70%" />
### Markdown4j Parser
`markdown4j` parser provides [YUML](http://yuml.me/) and [Websequence](https://www.websequencediagrams.com/) extensions
<img src="../assets/themes/zeppelin/img/docs-img/markdown-example-markdown4j-parser.png" width="70%" />
### Pegdown Parser
`pegdown` parser provides github flavored markdown.
<img src="../assets/themes/zeppelin/img/docs-img/markdown-example-pegdown-parser.png" width="70%" />

View file

@ -16,6 +16,9 @@
"defaultValue": "6123",
"description": "port of running JobManager."
}
},
"editor": {
"language": "scala"
}
}
]

View file

@ -210,7 +210,7 @@ public class JDBCInterpreter extends Interpreter {
}
}
if (null == connection) {
final Properties properties = propertiesMap.get(propertyKey);
final Properties properties = (Properties) propertiesMap.get(propertyKey).clone();
logger.info(properties.getProperty(DRIVER_KEY));
Class.forName(properties.getProperty(DRIVER_KEY));
final String url = properties.getProperty(URL_KEY);

View file

@ -154,6 +154,9 @@
"defaultValue": "org.postgresql.Driver",
"description": ""
}
},
"editor": {
"language": "sql"
}
}
]

View file

@ -130,12 +130,6 @@ public class LivyHelper {
}
}
protected void initializeSpark(final InterpreterContext context,
final Map<String, Integer> userSessionMap) throws Exception {
interpret("val sqlContext = new org.apache.spark.sql.SQLContext(sc)\n" +
"import sqlContext.implicits._", context, userSessionMap);
}
public InterpreterResult interpretInput(String stringLines,
final InterpreterContext context,
final Map<String, Integer> userSessionMap,

View file

@ -75,7 +75,6 @@ public class LivySparkInterpreter extends Interpreter {
interpreterContext,
"spark")
);
livyHelper.initializeSpark(interpreterContext, userSessionMap);
} catch (Exception e) {
LOGGER.error("Exception in LivySparkInterpreter while interpret ", e);
return new InterpreterResult(InterpreterResult.Code.ERROR, e.getMessage());

View file

@ -65,7 +65,6 @@ public class LivySparkSQLInterpreter extends Interpreter {
interpreterContext,
"spark")
);
livyHelper.initializeSpark(interpreterContext, userSessionMap);
} catch (Exception e) {
LOGGER.error("Exception in LivySparkSQLInterpreter while interpret ", e);
return new InterpreterResult(InterpreterResult.Code.ERROR, e.getMessage());

View file

@ -87,6 +87,9 @@
"defaultValue": "",
"description": "Adding extra libraries to livy interpreter"
}
},
"editor": {
"language": "scala"
}
},
{
@ -105,6 +108,9 @@
"defaultValue": "false",
"description": "Execute multiple SQL concurrently if set true."
}
},
"editor": {
"language": "sql"
}
},
{
@ -112,6 +118,9 @@
"name": "pyspark",
"className": "org.apache.zeppelin.livy.LivyPySparkInterpreter",
"properties": {
},
"editor": {
"language": "python"
}
},
{
@ -119,6 +128,9 @@
"name": "sparkr",
"className": "org.apache.zeppelin.livy.LivySparkRInterpreter",
"properties": {
},
"editor": {
"language": "r"
}
}
]

View file

@ -40,6 +40,11 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.commonjava.googlecode.markdown4j</groupId>
<artifactId>markdown4j</artifactId>
@ -47,8 +52,9 @@
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<groupId>org.pegdown</groupId>
<artifactId>pegdown</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>

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.markdown;
import org.markdown4j.Markdown4jProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/** Markdown Parser using markdown4j processor . */
public class Markdown4jParser implements MarkdownParser {
private Markdown4jProcessor processor;
public Markdown4jParser() {
processor = new Markdown4jProcessor();
}
@Override
public String render(String markdownText) {
String html = "";
try {
html = processor.process(markdownText);
} catch (IOException e) {
// convert checked exception to non-checked exception
throw new RuntimeException(e);
}
return html;
}
}

View file

@ -23,44 +23,78 @@ import java.util.Properties;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterUtils;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.markdown4j.Markdown4jProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Markdown interpreter for Zeppelin.
*/
public class Markdown extends Interpreter {
private Markdown4jProcessor md;
static final Logger LOGGER = LoggerFactory.getLogger(Markdown.class);
/** MarkdownInterpreter interpreter for Zeppelin. */
public class MarkdownInterpreter extends Interpreter {
private static final Logger LOGGER = LoggerFactory.getLogger(MarkdownInterpreter.class);
public Markdown(Properties property) {
private MarkdownParser parser;
/** Markdown Parser Type. */
public enum MarkdownParserType {
PEGDOWN {
@Override
public String toString() {
return PARSER_TYPE_PEGDOWN;
}
},
MARKDOWN4j {
@Override
public String toString() {
return PARSER_TYPE_MARKDOWN4J;
}
}
}
public static final String MARKDOWN_PARSER_TYPE = "markdown.parser.type";
public static final String PARSER_TYPE_PEGDOWN = "pegdown";
public static final String PARSER_TYPE_MARKDOWN4J = "markdown4j";
public MarkdownInterpreter(Properties property) {
super(property);
}
public static MarkdownParser createMarkdownParser(String parserType) {
LOGGER.debug("Creating " + parserType + " markdown interpreter");
if (MarkdownParserType.PEGDOWN.toString().equals(parserType)) {
return new PegdownParser();
} else {
/** default parser. */
return new Markdown4jParser();
}
}
@Override
public void open() {
md = new Markdown4jProcessor();
String parserType = getProperty(MARKDOWN_PARSER_TYPE);
parser = createMarkdownParser(parserType);
}
@Override
public void close() {}
@Override
public InterpreterResult interpret(String st, InterpreterContext interpreterContext) {
public InterpreterResult interpret(String markdownText, InterpreterContext interpreterContext) {
String html;
try {
html = md.process(st);
} catch (IOException | java.lang.RuntimeException e) {
LOGGER.error("Exception in Markdown while interpret ", e);
html = parser.render(markdownText);
} catch (RuntimeException e) {
LOGGER.error("Exception in MarkdownInterpreter while interpret ", e);
return new InterpreterResult(Code.ERROR, InterpreterUtils.getMostRelevantMessage(e));
}
return new InterpreterResult(Code.SUCCESS, "%html " + html);
}
@ -79,8 +113,8 @@ public class Markdown extends Interpreter {
@Override
public Scheduler getScheduler() {
return SchedulerFactory.singleton().createOrGetParallelScheduler(
Markdown.class.getName() + this.hashCode(), 5);
return SchedulerFactory.singleton()
.createOrGetParallelScheduler(MarkdownInterpreter.class.getName() + this.hashCode(), 5);
}
@Override

View file

@ -0,0 +1,23 @@
/*
* 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.markdown;
/** Abstract Markdown Parser. */
public interface MarkdownParser {
String render(String markdownText);
}

View file

@ -0,0 +1,56 @@
/*
* 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.markdown;
import org.pegdown.Extensions;
import org.pegdown.PegDownProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Markdown Parser using pegdown processor. */
public class PegdownParser implements MarkdownParser {
private PegDownProcessor processor;
public PegdownParser() {
int pegdownOptions = Extensions.ALL_WITH_OPTIONALS - Extensions.ANCHORLINKS;
int parsingTimeoutAsMillis = 5000;
processor = new PegDownProcessor(pegdownOptions, parsingTimeoutAsMillis);
}
@Override
public String render(String markdownText) {
String html = "";
String parsed = processor.markdownToHtml(markdownText);
if (null == parsed) {
throw new RuntimeException("Cannot parse markdown text to HTML using pegdown");
}
html = wrapWithMarkdownClassDiv(parsed);
return html;
}
/** wrap with markdown class div to styling DOM using css. */
public static String wrapWithMarkdownClassDiv(String html) {
return new StringBuilder()
.append("<div class=\"markdown-body\">\n")
.append(html)
.append("\n</div>")
.toString();
}
}

View file

@ -2,7 +2,17 @@
{
"group": "md",
"name": "md",
"className": "org.apache.zeppelin.markdown.Markdown",
"properties": null
"className": "org.apache.zeppelin.markdown.MarkdownInterpreter",
"properties": {
"markdown.parser.type": {
"envName": "MARKDOWN_PARSER_TYPE",
"propertyName": "markdown.parser.type",
"defaultValue": "markdown4j",
"description": "Markdown Parser Type. Available values: markdown4j, pegdown. Default = markdown4j"
}
},
"editor": {
"language": "markdown"
}
}
]

View file

@ -17,32 +17,35 @@
package org.apache.zeppelin.markdown;
import static org.junit.Assert.assertEquals;
import java.util.Properties;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.markdown.Markdown;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class MarkdownTest {
import java.util.Properties;
@Before
public void setUp() throws Exception {
}
import static org.junit.Assert.assertEquals;
@After
public void tearDown() throws Exception {
}
public class Markdown4jParserTest {
@Test
public void test() {
Markdown md = new Markdown(new Properties());
md.open();
InterpreterResult result = md.interpret("This is ~~deleted~~ text", null);
assertEquals("<p>This is <s>deleted</s> text</p>\n", result.message());
}
MarkdownInterpreter md;
@Before
public void setUp() throws Exception {
Properties props = new Properties();
props.put(MarkdownInterpreter.MARKDOWN_PARSER_TYPE, MarkdownInterpreter.PARSER_TYPE_MARKDOWN4J);
md = new MarkdownInterpreter(props);
md.open();
}
@After
public void tearDown() throws Exception {
md.close();
}
@Test
public void testStrikethrough() {
InterpreterResult result = md.interpret("This is ~~deleted~~ text", null);
assertEquals("<p>This is <s>deleted</s> text</p>\n", result.message());
}
}

View file

@ -0,0 +1,302 @@
/*
* 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.markdown;
import static org.junit.Assert.assertEquals;
import java.util.Properties;
import org.apache.zeppelin.interpreter.InterpreterResult;
import static org.apache.zeppelin.markdown.PegdownParser.wrapWithMarkdownClassDiv;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class PegdownParserTest {
MarkdownInterpreter md;
@Before
public void setUp() throws Exception {
Properties props = new Properties();
props.put(MarkdownInterpreter.MARKDOWN_PARSER_TYPE, MarkdownInterpreter.PARSER_TYPE_PEGDOWN);
md = new MarkdownInterpreter(props);
md.open();
}
@After
public void tearDown() throws Exception {
md.close();
}
@Test
public void testHeader() {
InterpreterResult r1 = md.interpret("# H1", null);
assertEquals(wrapWithMarkdownClassDiv("<h1>H1</h1>"), r1.message());
InterpreterResult r2 = md.interpret("## H2", null);
assertEquals(wrapWithMarkdownClassDiv("<h2>H2</h2>"), r2.message());
InterpreterResult r3 = md.interpret("### H3", null);
assertEquals(wrapWithMarkdownClassDiv("<h3>H3</h3>"), r3.message());
InterpreterResult r4 = md.interpret("#### H4", null);
assertEquals(wrapWithMarkdownClassDiv("<h4>H4</h4>"), r4.message());
InterpreterResult r5 = md.interpret("##### H5", null);
assertEquals(wrapWithMarkdownClassDiv("<h5>H5</h5>"), r5.message());
InterpreterResult r6 = md.interpret("###### H6", null);
assertEquals(wrapWithMarkdownClassDiv("<h6>H6</h6>"), r6.message());
InterpreterResult r7 = md.interpret("Alt-H1\n" + "======", null);
assertEquals(wrapWithMarkdownClassDiv("<h1>Alt-H1</h1>"), r7.message());
InterpreterResult r8 = md.interpret("Alt-H2\n" + "------", null);
assertEquals(wrapWithMarkdownClassDiv("<h2>Alt-H2</h2>"), r8.message());
}
@Test
public void testStrikethrough() {
InterpreterResult result = md.interpret("This is ~~deleted~~ text", null);
assertEquals(
wrapWithMarkdownClassDiv("<p>This is <del>deleted</del> text</p>"), result.message());
}
@Test
public void testItalics() {
InterpreterResult result = md.interpret("This is *italics* text", null);
assertEquals(
wrapWithMarkdownClassDiv("<p>This is <em>italics</em> text</p>"), result.message());
}
@Test
public void testStrongEmphasis() {
InterpreterResult result = md.interpret("This is **strong emphasis** text", null);
assertEquals(
wrapWithMarkdownClassDiv("<p>This is <strong>strong emphasis</strong> text</p>"),
result.message());
}
@Test
public void testOrderedList() {
String input =
new StringBuilder()
.append("1. First ordered list item\n")
.append("2. Another item")
.toString();
String expected =
new StringBuilder()
.append("<ol>\n")
.append(" <li>First ordered list item</li>\n")
.append(" <li>Another item</li>\n")
.append("</ol>")
.toString();
InterpreterResult result = md.interpret(input, null);
assertEquals(wrapWithMarkdownClassDiv(expected), result.message());
}
@Test
public void testUnorderedList() {
String input =
new StringBuilder()
.append("* Unordered list can use asterisks\n")
.append("- Or minuses\n")
.append("+ Or pluses")
.toString();
String expected =
new StringBuilder()
.append("<ul>\n")
.append(" <li>Unordered list can use asterisks</li>\n")
.append(" <li>Or minuses</li>\n")
.append(" <li>Or pluses</li>\n")
.append("</ul>")
.toString();
InterpreterResult result = md.interpret(input, null);
assertEquals(wrapWithMarkdownClassDiv(expected), result.message());
}
@Test
public void testLinks() {
String input =
new StringBuilder()
.append("[I'm an inline-style link](https://www.google.com)\n")
.append("\n")
.append(
"[I'm an inline-style link with title](https://www.google.com \"Google's Homepage\")\n")
.append("\n")
.append("[I'm a reference-style link][Arbitrary case-insensitive reference text]\n")
.append("\n")
.append("[I'm a relative reference to a repository file](../blob/master/LICENSE)\n")
.append("\n")
.append("[You can use numbers for reference-style link definitions][1]\n")
.append("\n")
.append("Or leave it empty and use the [link text itself].\n")
.append("\n")
.append("URLs and URLs in angle brackets will automatically get turned into links. \n")
.append("http://www.example.com or <http://www.example.com> and sometimes \n")
.append("example.com (but not on Github, for example).\n")
.append("\n")
.append("Some text to show that the reference links can follow later.\n")
.append("\n")
.append("[arbitrary case-insensitive reference text]: https://www.mozilla.org\n")
.append("[1]: http://slashdot.org\n")
.append("[link text itself]: http://www.reddit.com")
.toString();
String expected =
new StringBuilder()
.append(
"<p><a href=\"https://www.google.com\">I&rsquo;m an inline-style link</a></p>\n")
.append(
"<p><a href=\"https://www.google.com\" title=\"Google&#39;s Homepage\">I&rsquo;m an inline-style link with title</a></p>\n")
.append(
"<p><a href=\"https://www.mozilla.org\">I&rsquo;m a reference-style link</a></p>\n")
.append(
"<p><a href=\"../blob/master/LICENSE\">I&rsquo;m a relative reference to a repository file</a></p>\n")
.append(
"<p><a href=\"http://slashdot.org\">You can use numbers for reference-style link definitions</a></p>\n")
.append(
"<p>Or leave it empty and use the <a href=\"http://www.reddit.com\">link text itself</a>.</p>\n")
.append(
"<p>URLs and URLs in angle brackets will automatically get turned into links.<br/><a href=\"http://www.example.com\">http://www.example.com</a> or <a href=\"http://www.example.com\">http://www.example.com</a> and sometimes<br/>example.com (but not on Github, for example).</p>\n")
.append("<p>Some text to show that the reference links can follow later.</p>")
.toString();
InterpreterResult result = md.interpret(input, null);
assertEquals(wrapWithMarkdownClassDiv(expected), result.message());
}
@Test
public void testInlineCode() {
InterpreterResult result = md.interpret("Inline `code` has `back-ticks around` it.", null);
assertEquals(
wrapWithMarkdownClassDiv(
"<p>Inline <code>code</code> has <code>back-ticks around</code> it.</p>"),
result.message());
}
@Test
public void testBlockQuotes() {
InterpreterResult r1 =
md.interpret(
"> Blockquotes are very handy in email to emulate reply text.\n"
+ "> This line is part of the same quote.",
null);
assertEquals(
wrapWithMarkdownClassDiv(
"<blockquote>\n"
+ " <p>Blockquotes are very handy in email to emulate reply text.<br/>This line is part of the same quote.</p>\n"
+ "</blockquote>"),
r1.message());
InterpreterResult r2 =
md.interpret(
"> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **MarkdownInterpreter** into a blockquote. ",
null);
assertEquals(
wrapWithMarkdownClassDiv(
"<blockquote>\n"
+ " <p>This is a very long line that will still be quoted properly when it wraps. Oh boy let&rsquo;s keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can <em>put</em> <strong>MarkdownInterpreter</strong> into a blockquote. </p>\n"
+ "</blockquote>"),
r2.message());
}
@Test
public void testSimpleTable() {
String input =
new StringBuilder()
.append("MarkdownInterpreter | Less | Pretty\n")
.append("--- | --- | ---\n")
.append("*Still* | `renders` | **nicely**\n")
.append("1 | 2 | 3")
.toString();
String expected =
new StringBuilder()
.append("<table>\n")
.append(" <thead>\n")
.append(" <tr>\n")
.append(" <th>MarkdownInterpreter </th>\n")
.append(" <th>Less </th>\n")
.append(" <th>Pretty</th>\n")
.append(" </tr>\n")
.append(" </thead>\n")
.append(" <tbody>\n")
.append(" <tr>\n")
.append(" <td><em>Still</em> </td>\n")
.append(" <td><code>renders</code> </td>\n")
.append(" <td><strong>nicely</strong></td>\n")
.append(" </tr>\n")
.append(" <tr>\n")
.append(" <td>1 </td>\n")
.append(" <td>2 </td>\n")
.append(" <td>3</td>\n")
.append(" </tr>\n")
.append(" </tbody>\n")
.append("</table>")
.toString();
InterpreterResult result = md.interpret(input, null);
assertEquals(wrapWithMarkdownClassDiv(expected), result.message());
}
@Test
public void testAlignedTable() {
String input =
new StringBuilder()
.append("| First Header | Second Header | Third Header |\n")
.append("| :----------- | :-----------: | -------------------: |\n")
.append("| First row | Data | Very long data entry |\n")
.append("| Second row | **Cell** | *Cell* |")
.toString();
String expected =
new StringBuilder()
.append("<table>\n")
.append(" <thead>\n")
.append(" <tr>\n")
.append(" <th align=\"left\">First Header </th>\n")
.append(" <th align=\"center\">Second Header </th>\n")
.append(" <th align=\"right\">Third Header </th>\n")
.append(" </tr>\n")
.append(" </thead>\n")
.append(" <tbody>\n")
.append(" <tr>\n")
.append(" <td align=\"left\">First row </td>\n")
.append(" <td align=\"center\">Data </td>\n")
.append(" <td align=\"right\">Very long data entry </td>\n")
.append(" </tr>\n")
.append(" <tr>\n")
.append(" <td align=\"left\">Second row </td>\n")
.append(" <td align=\"center\"><strong>Cell</strong> </td>\n")
.append(" <td align=\"right\"><em>Cell</em> </td>\n")
.append(" </tr>\n")
.append(" </tbody>\n")
.append("</table>")
.toString();
InterpreterResult result = md.interpret(input, null);
assertEquals(wrapWithMarkdownClassDiv(expected), result.message());
}
}

View file

@ -16,6 +16,9 @@
"defaultValue": "1000",
"description": "Max number of dataframe rows to display."
}
},
"editor": {
"language": "python"
}
},
{

View file

@ -28,6 +28,9 @@
"defaultValue": "",
"description": "Kerberos principal"
}
},
"editor": {
"language": "sh"
}
}
]
]

View file

@ -118,7 +118,7 @@ public class SparkInterpreter extends Interpreter {
private Map<String, Object> binder;
private SparkVersion sparkVersion;
private File outputDir; // class outputdir for scala 2.11
private static File outputDir; // class outputdir for scala 2.11
private Object classServer; // classserver for scala 2.11
@ -603,8 +603,11 @@ public class SparkInterpreter extends Interpreter {
sparkReplClassDir = System.getProperty("java.io.tmpdir");
}
outputDir = createTempDir(sparkReplClassDir);
synchronized (sharedInterpreterLock) {
if (outputDir == null) {
outputDir = createTempDir(sparkReplClassDir);
}
}
argList.add("-Yrepl-class-based");
argList.add("-Yrepl-outdir");
argList.add(outputDir.getAbsolutePath());
@ -1307,7 +1310,12 @@ public class SparkInterpreter extends Interpreter {
logger.info("Close interpreter");
if (numReferenceOfSparkContext.decrementAndGet() == 0) {
sc.stop();
if (sparkSession != null) {
Utils.invokeMethod(sparkSession, "stop");
} else if (sc != null){
sc.stop();
}
sparkSession = null;
sc = null;
if (classServer != null) {
Utils.invokeMethod(classServer, "stop");

View file

@ -24,6 +24,7 @@ import static scala.collection.JavaConversions.collectionAsScalaIterable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -55,6 +56,7 @@ public class ZeppelinContext {
private SparkDependencyResolver dep;
private InterpreterContext interpreterContext;
private int maxResult;
private List<Class> supportedClasses;
public ZeppelinContext(SparkContext sc, SQLContext sql,
InterpreterContext interpreterContext,
@ -65,6 +67,25 @@ public class ZeppelinContext {
this.interpreterContext = interpreterContext;
this.dep = dep;
this.maxResult = maxResult;
this.supportedClasses = new ArrayList<>();
try {
supportedClasses.add(this.getClass().forName("org.apache.spark.sql.Dataset"));
} catch (ClassNotFoundException e) {
}
try {
supportedClasses.add(this.getClass().forName("org.apache.spark.sql.DataFrame"));
} catch (ClassNotFoundException e) {
}
try {
supportedClasses.add(this.getClass().forName("org.apache.spark.sql.SchemaRDD"));
} catch (ClassNotFoundException e) {
}
if (supportedClasses.isEmpty()) {
throw new InterpreterException("Can not road Dataset/DataFrame/SchemaRDD class");
}
}
public SparkContext sc;
@ -161,33 +182,8 @@ public class ZeppelinContext {
@ZeppelinApi
public void show(Object o, int maxResult) {
Class cls = null;
try {
cls = this.getClass().forName("org.apache.spark.sql.Dataset");
} catch (ClassNotFoundException e) {
}
if (cls == null) {
try {
cls = this.getClass().forName("org.apache.spark.sql.DataFrame");
} catch (ClassNotFoundException e) {
}
}
if (cls == null) {
try {
cls = this.getClass().forName("org.apache.spark.sql.SchemaRDD");
} catch (ClassNotFoundException e) {
}
}
if (cls == null) {
throw new InterpreterException("Can not road Dataset/DataFrame/SchemaRDD class");
}
try {
if (cls.isInstance(o)) {
if (supportedClasses.contains(o.getClass())) {
interpreterContext.out.write(showDF(sc, interpreterContext, o, maxResult));
} else {
interpreterContext.out.write(o.toString());
@ -210,6 +206,12 @@ public class ZeppelinContext {
sc.setJobGroup(jobGroup, "Zeppelin", false);
try {
// convert it to DataFrame if it is Dataset, as we will iterate all the records
// and assume it is type Row.
if (df.getClass().getCanonicalName().equals("org.apache.spark.sql.Dataset")) {
Method convertToDFMethod = df.getClass().getMethod("toDF");
df = convertToDFMethod.invoke(df);
}
take = df.getClass().getMethod("take", int.class);
rows = (Object[]) take.invoke(df, maxResult + 1);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException

View file

@ -54,6 +54,9 @@
"defaultValue": "local[*]",
"description": "Spark master uri. ex) spark://masterhost:7077"
}
},
"editor": {
"language": "scala"
}
},
{
@ -85,6 +88,9 @@
"defaultValue": "true",
"description": "Import implicits, UDF collection, and sql if set true. true by default."
}
},
"editor": {
"language": "sql"
}
},
{
@ -104,6 +110,9 @@
"defaultValue": "spark-packages,http://dl.bintray.com/spark-packages/maven,false;",
"description": "A list of 'id,remote-repository-URL,is-snapshot;' for each remote repository."
}
},
"editor": {
"language": "scala"
}
},
{
@ -117,6 +126,9 @@
"defaultValue": "python",
"description": "Python command to run pyspark with"
}
},
"editor": {
"language": "python"
}
}
]

View file

@ -218,11 +218,12 @@ java_import(gateway.jvm, "scala.Tuple2")
jconf = intp.getSparkConf()
conf = SparkConf(_jvm = gateway.jvm, _jconf = jconf)
sc = SparkContext(jsc=jsc, gateway=gateway, conf=conf)
sqlc = SQLContext(sc, intp.getSQLContext())
sqlContext = sqlc
if sparkVersion.isSpark2():
spark = SparkSession(sc, intp.getSparkSession())
sqlc = spark._wrapped
else:
sqlc = SQLContext(sparkContext=sc, sqlContext=intp.getSQLContext())
sqlContext = sqlc
completion = PySparkCompletion(intp)
z = PyZeppelinContext(intp.getZeppelinContext())

View file

@ -54,6 +54,9 @@
"defaultValue": "local[*]",
"description": "Spark master uri. ex) spark://masterhost:7077"
}
},
"editor": {
"language": "scala"
}
},
{
@ -85,6 +88,9 @@
"defaultValue": "true",
"description": "Import implicits, UDF collection, and sql if set true. true by default."
}
},
"editor": {
"language": "sql"
}
},
{
@ -104,6 +110,9 @@
"defaultValue": "spark-packages,http://dl.bintray.com/spark-packages/maven,false;",
"description": "A list of 'id,remote-repository-URL,is-snapshot;' for each remote repository."
}
},
"editor": {
"language": "scala"
}
},
{
@ -117,6 +126,9 @@
"defaultValue": "python",
"description": "Python command to run pyspark with"
}
},
"editor": {
"language": "python"
}
},
{
@ -148,6 +160,9 @@
"defaultValue": "out.format = 'html', comment = NA, echo = FALSE, results = 'asis', message = F, warning = F",
"description": ""
}
},
"editor": {
"language": "r"
}
}
]

View file

@ -115,6 +115,9 @@ The following components are provided under Apache License.
(Apache 2.0) Servlet API (org.mortbay.jetty:servlet-api:2.5-20081211 - https://en.wikipedia.org/wiki/Jetty_(web_server))
(Apache 2.0) Google HTTP Client Library for Java (com.google.http-client:google-http-client-jackson2:1.21.0 - https://github.com/google/google-http-java-client/tree/dev/google-http-client-jackson2)
(Apache 2.0) angular-esri-map (https://github.com/Esri/angular-esri-map)
(Apache 2.0) pegdown (org.pegdown:pegdown:1.6.0 - https://github.com/sirthias/pegdown)
(Apache 2.0) parboiled-java (org.parboiled:parboiled-java:1.1.7 - https://github.com/sirthias/parboiled)
(Apache 2.0) parboiled-core (org.parboiled:parboiled-core:1.1.7 - https://github.com/sirthias/parboiled)
========================================================================
MIT licenses
@ -151,6 +154,7 @@ The text of each license is also included at licenses/LICENSE-[project]-[version
(The MIT License) bcprov-jdk15on v1.51 (org.bouncycastle:bcprov-jdk15on:jar:1.51 - http://www.bouncycastle.org/java.html) - http://www.bouncycastle.org/licence.html
(The MIT License) AnchorJS (https://github.com/bryanbraun/anchorjs) - https://github.com/bryanbraun/anchorjs/blob/master/README.md#license
(The MIT License) moment-duration-format v1.3.0 (https://github.com/jsmreese/moment-duration-format) - https://github.com/jsmreese/moment-duration-format/blob/master/LICENSE
(The MIT License) github-markdown-css 2.4.0 (https://github.com/sindresorhus/github-markdown-css) - https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/license
The following components are provided under the MIT License.
@ -191,10 +195,13 @@ The following components are provided under the BSD-style License.
(BSD-like) Scala Actors library (org.scala-lang:scala-actors:2.11.7 - http://www.scala-lang.org/)
(BSD-like) Scala Compiler (org.scala-lang:scala-compiler:2.11.7 - http://www.scala-lang.org/)
(BSD-like) Scala Compiler (org.scala-lang:scala-reflect:2.11.7 - http://www.scala-lang.org/)
(BSD-like) ASM (asm:asm:jar:3.1 - http://asm.ow2.org/) - Copyright (c) 2000-2011 INRIA, France Telecom
(BSD-like) ASM asm (asm:asm:jar:3.1 - http://asm.ow2.org/) - Copyright (c) 2000-2011 INRIA, France Telecom
(BSD-like) ASM asm-tree (org.ow2.asm:asm-tree:5.0.3 - http://asm.ow2.org/) - Copyright (c) 2000-2011 INRIA, France Telecom
(BSD-like) ASM asm-analysis (org.ow2.asm:asm-analysis:5.0.3 - http://asm.ow2.org/) - Copyright (c) 2000-2011 INRIA, France Telecom
(BSD-like) ASM asm-utils (org.ow2.asm:asm-utils:5.0.3 - http://asm.ow2.org/) - Copyright (c) 2000-2011 INRIA, France Telecom
(New BSD License) Markdown4j (org.commonjava.googlecode.markdown4j:markdown4j:jar:2.2-cj-1.0 - https://code.google.com/p/markdown4j/)
(New BSD License) Py4J (net.sf.py4j:py4j:0.9 - http://py4j.sourceforge.net/)
(New BSD License) Py4J (net.sf.py4j:py4j:0.10.1 - http://py4j.sourceforge.net/) - https://github.com/bartdag/py4j/blob/0.10.1/LICENSE.txt
(New BSD License) Markdown4j (org.commonjava.googlecode.markdown4j:markdown4j:jar:2.2-cj-1.0 - https://code.google.com/p/markdown4j/)
(BSD 3 Clause) Paranamer (com.thoughtworks.paranamer:paranamer:jar:2.6) - https://github.com/paul-hammant/paranamer/blob/paranamer-parent-2.6/LICENSE.txt
(BSD 3 Clause) netlib core (com.github.fommil.netlib:core:1.1.2 - https://github.com/fommil/netlib-java/core)
(BSD 3 Clause) JPMML-Model (org.jpmml:pmml-model:1.2.7 - https://github.com/jpmml/jpmml-model)

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -104,6 +104,21 @@ public class DependencyResolver extends AbstractDependencyResolver {
return libs;
}
public synchronized void copyLocalDependency(String srcPath, File destPath)
throws IOException {
if (StringUtils.isBlank(srcPath)) {
return;
}
File srcFile = new File(srcPath);
File destFile = new File(destPath, srcFile.getName());
if (!destFile.exists() || !FileUtils.contentEquals(srcFile, destFile)) {
FileUtils.copyFile(srcFile, destFile);
logger.info("copy {} to {}", srcFile.getAbsolutePath(), destPath);
}
}
private List<File> loadFromMvn(String artifact, Collection<String> excludes)
throws RepositoryException {
Collection<String> allExclusions = new LinkedList<String>();

View file

@ -251,6 +251,7 @@ public abstract class Interpreter {
private String className;
private boolean defaultInterpreter;
private Map<String, InterpreterProperty> properties;
private Map<String, Object> editor;
private String path;
public RegisteredInterpreter(String name, String group, String className,
@ -266,6 +267,7 @@ public abstract class Interpreter {
this.className = className;
this.defaultInterpreter = defaultInterpreter;
this.properties = properties;
this.editor = new HashMap<>();
}
public String getName() {
@ -292,6 +294,10 @@ public abstract class Interpreter {
return properties;
}
public Map<String, Object> getEditor() {
return editor;
}
public void setPath(String path) {
this.path = path;
}

View file

@ -504,11 +504,13 @@ public class RemoteInterpreterServer
return new InterpreterOutput(new InterpreterOutputListener() {
@Override
public void onAppend(InterpreterOutput out, byte[] line) {
logger.debug("Output Append:" + new String(line));
eventClient.onInterpreterOutputAppend(noteId, paragraphId, new String(line));
}
@Override
public void onUpdate(InterpreterOutput out, byte[] output) {
logger.debug("Output Update:" + new String(output));
eventClient.onInterpreterOutputUpdate(noteId, paragraphId, new String(output));
}
});

View file

@ -250,6 +250,9 @@ public class NotebookServer extends WebSocketServlet implements
case SAVE_INTERPRETER_BINDINGS:
saveInterpreterBindings(conn, messagereceived);
break;
case EDITOR_SETTING:
getEditorSetting(conn, messagereceived);
break;
default:
break;
}
@ -1457,7 +1460,7 @@ public class NotebookServer extends WebSocketServlet implements
}
/**
* This callback is for praragraph that runs on RemoteInterpreterProcess
* This callback is for paragraph that runs on RemoteInterpreterProcess
* @param paragraph
* @param out
* @param output
@ -1569,11 +1572,23 @@ public class NotebookServer extends WebSocketServlet implements
if (id.equals(interpreterGroupId)) {
broadcast(
note.getId(),
new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put(
"noteId", noteId).put("paragraphId", paragraphId));
new Message(OP.ANGULAR_OBJECT_REMOVE)
.put("name", name)
.put("noteId", noteId)
.put("paragraphId", paragraphId));
}
}
}
}
private void getEditorSetting(NotebookSocket conn, Message fromMessage)
throws IOException {
String replName = (String) fromMessage.get("magic");
String noteId = getOpenNoteId(conn);
Message resp = new Message(OP.EDITOR_SETTING);
resp.put("editor", notebook().getInterpreterFactory().getEditorSetting(noteId, replName));
conn.send(serializeMessage(resp));
return;
}
}

View file

@ -154,7 +154,7 @@ public abstract class AbstractTestRestApi {
// set spark master and other properties
sparkIntpSetting.getProperties().setProperty("master", "spark://" + getHostname() + ":7071");
sparkIntpSetting.getProperties().setProperty("spark.cores.max", "2");
sparkIntpSetting.getProperties().setProperty("zeppelin.spark.useHiveContext", "false");
// set spark home for pyspark
sparkIntpSetting.getProperties().setProperty("spark.home", getSparkHome());
pySpark = true;
@ -171,10 +171,16 @@ public abstract class AbstractTestRestApi {
String sparkHome = getSparkHome();
if (sparkHome != null) {
sparkIntpSetting.getProperties().setProperty("master", "spark://" + getHostname() + ":7071");
if (System.getenv("SPARK_MASTER") != null) {
sparkIntpSetting.getProperties().setProperty("master", System.getenv("SPARK_MASTER"));
} else {
sparkIntpSetting.getProperties()
.setProperty("master", "spark://" + getHostname() + ":7071");
}
sparkIntpSetting.getProperties().setProperty("spark.cores.max", "2");
// set spark home for pyspark
sparkIntpSetting.getProperties().setProperty("spark.home", sparkHome);
sparkIntpSetting.getProperties().setProperty("zeppelin.spark.useHiveContext", "false");
pySpark = true;
sparkR = true;
}
@ -194,7 +200,11 @@ public abstract class AbstractTestRestApi {
}
private static String getSparkHome() {
String sparkHome = getSparkHomeRecursively(new File(System.getProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HOME.getVarName())));
String sparkHome = System.getenv("SPARK_HOME");
if (sparkHome != null) {
return sparkHome;
}
sparkHome = getSparkHomeRecursively(new File(System.getProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HOME.getVarName())));
System.out.println("SPARK HOME detected " + sparkHome);
return sparkHome;
}

View file

@ -17,6 +17,7 @@
package org.apache.zeppelin.rest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
@ -24,6 +25,7 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Paragraph;
@ -82,6 +84,57 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
ZeppelinServer.notebook.removeNote(note.getId(), null);
}
@Test
public void sparkSQLTest() throws IOException {
// create new note
Note note = ZeppelinServer.notebook.createNote(null);
int sparkVersion = getSparkVersionNumber(note);
// DataFrame API is available from spark 1.3
if (sparkVersion >= 13) {
// test basic dataframe api
Paragraph p = note.addParagraph();
Map config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%spark val df=sqlContext.createDataFrame(Seq((\"hello\",20)))\n" +
"df.collect()");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertTrue(p.getResult().message().contains(
"Array[org.apache.spark.sql.Row] = Array([hello,20])"));
// test display DataFrame
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%spark val df=sqlContext.createDataFrame(Seq((\"hello\",20)))\n" +
"z.show(df)");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals(InterpreterResult.Type.TABLE, p.getResult().type());
assertEquals("_1\t_2\nhello\t20\n", p.getResult().message());
// test display DataSet
if (sparkVersion >= 20) {
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%spark val ds=spark.createDataset(Seq((\"hello\",20)))\n" +
"z.show(ds)");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals(InterpreterResult.Type.TABLE, p.getResult().type());
assertEquals("_1\t_2\nhello\t20\n", p.getResult().message());
}
ZeppelinServer.notebook.removeNote(note.getId(), null);
}
}
@Test
public void sparkRTest() throws IOException {
// create new note
@ -135,11 +188,78 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
config.put("enabled", true);
p.setConfig(config);
p.setText("%pyspark print(sc.parallelize(range(1, 11)).reduce(lambda a, b: a + b))");
// p.getRepl("org.apache.zeppelin.spark.SparkInterpreter").open();
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals("55\n", p.getResult().message());
if (sparkVersion >= 13) {
// run sqlContext test
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%pyspark from pyspark.sql import Row\n" +
"df=sqlContext.createDataFrame([Row(id=1, age=20)])\n" +
"df.collect()");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals("[Row(age=20, id=1)]\n", p.getResult().message());
// test display Dataframe
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%pyspark from pyspark.sql import Row\n" +
"df=sqlContext.createDataFrame([Row(id=1, age=20)])\n" +
"z.show(df)");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals(InterpreterResult.Type.TABLE, p.getResult().type());
// TODO (zjffdu), one more \n is appended, need to investigate why.
assertEquals("age\tid\n20\t1\n\n", p.getResult().message());
// test udf
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%pyspark sqlContext.udf.register(\"f1\", lambda x: len(x))\n" +
"sqlContext.sql(\"select f1(\\\"abc\\\") as len\").collect()");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals("[Row(len=u'3')]\n", p.getResult().message());
}
if (sparkVersion >= 20) {
// run SparkSession test
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
p.setText("%pyspark from pyspark.sql import Row\n" +
"df=sqlContext.createDataFrame([Row(id=1, age=20)])\n" +
"df.collect()");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals("[Row(age=20, id=1)]\n", p.getResult().message());
// test udf
p = note.addParagraph();
config = p.getConfig();
config.put("enabled", true);
p.setConfig(config);
// use SQLContext to register UDF but use this UDF through SparkSession
p.setText("%pyspark sqlContext.udf.register(\"f1\", lambda x: len(x))\n" +
"spark.sql(\"select f1(\\\"abc\\\") as len\").collect()");
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
assertEquals("[Row(len=u'3')]\n", p.getResult().message());
}
}
ZeppelinServer.notebook.removeNote(note.getId(), null);
}
@ -166,7 +286,6 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
p.setText("%pyspark\nfrom pyspark.sql.functions import *\n"
+ "print(" + sqlContextName + ".range(0, 10).withColumn('uniform', rand(seed=10) * 3.14).count())");
// p.getRepl("org.apache.zeppelin.spark.SparkInterpreter").open();
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());
@ -257,6 +376,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
assertEquals(Status.FINISHED, p1.getStatus());
assertEquals("2\n", p1.getResult().message());
}
ZeppelinServer.notebook.removeNote(note.getId(), null);
}
/**
@ -270,7 +390,6 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
config.put("enabled", true);
p.setConfig(config);
p.setText("%spark print(sc.version)");
// p.getRepl("org.apache.zeppelin.spark.SparkInterpreter").open();
note.run(p.getId());
waitForFinish(p);
assertEquals(Status.FINISHED, p.getStatus());

View file

@ -43,4 +43,4 @@ log4j.logger.DataNucleus.Datastore=ERROR
# Log all JDBC parameters
log4j.logger.org.hibernate.type=ALL
log4j.logger.org.apache.zeppelin.interpreter.remote.RemoteInterpreter=DEBUG

View file

@ -24,7 +24,7 @@
"angular-elastic": "~2.4.2",
"angular-elastic-input": "~2.2.0",
"angular-xeditable": "0.1.12",
"highlightjs": "^9.2.0",
"highlightjs": "^9.4.0",
"lodash": "~3.9.3",
"angular-filter": "~0.5.4",
"ngtoast": "~2.0.0",
@ -33,7 +33,8 @@
"handsontable": "~0.24.2",
"moment-duration-format": "^1.3.0",
"select2": "^4.0.3",
"angular-esri-map": "~2.0.0"
"angular-esri-map": "~2.0.0",
"github-markdown-css": "^2.4.0"
},
"devDependencies": {
"angular-mocks": "1.5.0"
@ -61,7 +62,7 @@
"highlight.pack.js",
"styles/github.css"
],
"version": "8.4.0",
"version": "9.4.0",
"name": "highlightjs"
}
}

View file

@ -35,8 +35,8 @@ limitations under the License.
</select>
</div>
<b>Option</b>
<div>
<h5>Option</h5>
<span class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle"
data-toggle="dropdown">
@ -68,130 +68,144 @@ limitations under the License.
</span>
<span>Interpreter for note</span>
</div>
<br />
<div class="col-md-12" style="padding-left:0px">
<div class="checkbox">
<span class="input-group" style="line-height:30px;">
<label><input type="checkbox" style="width:20px" ng-model="newInterpreterSetting.option.isExistingProcess"/> Connect to existing process </label>
</span>
</div>
</div>
<div ng-show="newInterpreterSetting.option.isExistingProcess" class="form-group" style="width:200px">
<b>Host</b>
<input id="newInterpreterSettingHost" input pu-elastic-input
pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.host" />
</div>
<div ng-show="newInterpreterSetting.option.isExistingProcess" class="form-group" style="width:200px">
<b>Port</b>
<input id="newInterpreterSettingPort" input pu-elastic-input
pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port" />
</div>
<div class="col-md-12">
<div class="checkbox">
<div class="row interpreter" style="margin-top: 5px;">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">
<label><input type="checkbox" style="width:18px !important" id="idShowPermission" ng-click="togglePermissions('newInterpreter')" ng-model="newInterpreterSetting.option.setPermission"/>
Set permission </label>
<label>
<input type="checkbox" style="width:20px" id="isExistingProcess" ng-model="newInterpreterSetting.option.isExistingProcess"/>
Connect to existing process
</label>
</span>
</div>
</div>
</div>
<div class="row interpreter" ng-show="newInterpreterSetting.option.isExistingProcess" >
<div class="col-md-12">
<b>Host</b>
<input id="newInterpreterSettingHost" input pu-elastic-input
pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.host"/>
</div>
<div class="col-md-12">
<b>Port</b>
<input id="newInterpreterSettingPort" input pu-elastic-input
pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port"/>
</div>
</div>
<div class="row interpreter">
<div class="col-md-12">
<div class="checkbox remove-margin-top-bottom">
<span class="input-group" style="line-height:30px;">
<label>
<input type="checkbox" style="width:20px !important" id="idShowPermission" ng-click="togglePermissions('newInterpreter')" ng-model="newInterpreterSetting.option.setPermission"/>
Set permission
</label>
</span>
</div>
</div>
</div>
<br/>
<div class="col-md-12">
<!-- permissions -->
<div ng-show="newInterpreterSetting.option.setPermission" class="permissionsForm">
<div>
<p>
Enter comma separated users in the fields. <br />
Empty field (*) implies anyone can run this interpreter.
</p>
<div class="row interpreter">
<div class="col-md-12">
<!-- permissions -->
<div ng-show="newInterpreterSetting.option.setPermission" class="permissionsForm">
<div>
<span class="owners">Owners </span>
<select id="newInterpreterUsers" class="form-control" multiple="multiple">
<option ng-repeat="user in newInterpreterSetting.option.users" selected="selected">{{user}}</option>
</select>
<p>
Enter comma separated users in the fields. <br />
Empty field (*) implies anyone can run this interpreter.
</p>
<div>
<span class="owners">Owners </span>
<select id="newInterpreterUsers" class="form-control" multiple="multiple">
<option ng-repeat="user in newInterpreterSetting.option.users" selected="selected">{{user}}</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div>
<h5>Properties</h5>
<table class="table table-striped properties">
<tr>
<th>name</th>
<th>value</th>
<th>description</th>
<th>action</th>
</tr>
<tr ng-repeat="(key, value) in newInterpreterSetting.properties">
<td>{{key}}</td>
<td><textarea msd-elastic ng-model="value.value"></textarea></td>
<td>{{value.description}}</td>
<td>
<button class="btn btn-default btn-sm fa fa-remove" ng-click="removeInterpreterProperty(key)">
</button>
</td>
</tr>
<b>Properties</b>
<table class="table table-striped properties">
<tr>
<th>name</th>
<th>value</th>
<th>description</th>
<th>action</th>
</tr>
<tr ng-repeat="(key, value) in newInterpreterSetting.properties">
<td>{{key}}</td>
<td><textarea msd-elastic ng-model="value.value"></textarea></td>
<td>{{value.description}}</td>
<td>
<button class="btn btn-default btn-sm fa fa-remove" ng-click="removeInterpreterProperty(key)">
</button>
</td>
</tr>
<tr>
<td>
<input pu-elastic-input pu-elastic-input-minwidth="180px"
ng-model="newInterpreterSetting.propertyKey" />
</td>
<td><textarea msd-elastic ng-model="newInterpreterSetting.propertyValue"></textarea></td>
<td></td>
<td>
<button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterProperty()">
</button>
</td>
</tr>
</table>
</div>
<tr>
<td>
<input pu-elastic-input pu-elastic-input-minwidth="180px"
ng-model="newInterpreterSetting.propertyKey" />
</td>
<td><textarea msd-elastic ng-model="newInterpreterSetting.propertyValue"></textarea></td>
<td></td>
<td>
<button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterProperty()">
</button>
</td>
</tr>
</table>
<div>
<h5>Dependencies</h5>
<table class="table table-striped properties">
<tr>
<th>artifact</th>
<th>exclude</th>
<th>action</th>
</tr>
<b>Dependencies</b>
<table class="table table-striped properties">
<tr>
<th>artifact</th>
<th>exclude</th>
<th>action</th>
</tr>
<tr ng-repeat="dep in newInterpreterSetting.dependencies">
<td>
<input ng-model="dep.groupArtifactVersion" style="width:100%" />
</td>
<td>
<textarea msd-elastic ng-model="dep.exclusions"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<button class="btn btn-default btn-sm fa fa-remove"
ng-click="removeInterpreterDependency(dep.groupArtifactVersion)">
</button>
</td>
</tr>
<tr ng-repeat="dep in newInterpreterSetting.dependencies">
<td>
<input ng-model="dep.groupArtifactVersion" style="width:100%" />
</td>
<td>
<textarea msd-elastic ng-model="dep.exclusions"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<button class="btn btn-default btn-sm fa fa-remove"
ng-click="removeInterpreterDependency(dep.groupArtifactVersion)">
</button>
</td>
</tr>
<tr>
<td>
<input ng-model="newInterpreterSetting.depArtifact"
placeholder="groupId:artifactId:version or local file path"
style="width: 100%" />
</td>
<td>
<textarea msd-elastic ng-model="newInterpreterSetting.depExclude"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterDependency()">
</button>
</td>
</tr>
</table>
<tr>
<td>
<input ng-model="newInterpreterSetting.depArtifact"
placeholder="groupId:artifactId:version or local file path"
style="width: 100%" />
</td>
<td>
<textarea msd-elastic ng-model="newInterpreterSetting.depExclude"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterDependency()">
</button>
</td>
</tr>
</table>
</div>
<span class="btn btn-primary" ng-click="addNewInterpreterSetting()">
Save

View file

@ -97,9 +97,10 @@ limitations under the License.
<small>
<span style="display:inline-block" ng-repeat="interpreter in setting.interpreterGroup"
title="{{interpreter.class}}">
<span ng-show="!$first">, </span>%<span ng-show="!$parent.$first || $first">{{setting.name}}</span>
<span ng-show="(!$parent.$first || $first) && !$first">.</span>
<span ng-show="!$first">{{interpreter.name}}</span>
<span ng-show="!$first">, </span>
%<span ng-show="!$parent.$first || $first">{{setting.name}}</span
><span ng-show="(!$parent.$first || $first) && !$first">.</span
><span ng-show="!$first">{{interpreter.name}}</span>
<span ng-show="$parent.$first && $first">(default)</span>
</span>
</small>
@ -124,7 +125,7 @@ limitations under the License.
</small>
</h3>
<span style="float:right">
<span style="float:right" ng-show="!valueform.$visible" >
<button class="btn btn-default btn-xs"
ng-click="valueform.$show();
copyOriginInterpreterSettingProperties(setting.id)">
@ -134,7 +135,7 @@ limitations under the License.
<span class="fa fa-refresh"></span> restart</button>
<button class="btn btn-default btn-xs"
ng-click="removeInterpreterSetting(setting.id)">
<span class="fa fa-remove"></span> remove</button>
<span class="fa fa-trash"></span> remove</button>
</span>
</div>
</div>
@ -229,12 +230,12 @@ limitations under the License.
</div>
</div>
</div>
</div>
<div ng-show="_.isEmpty(setting.properties) && _.isEmpty(setting.dependencies) || valueform.$hidden" class="col-md-12 gray40-message">
<div class="row interpreter" style="margin-top:20px !important">
<div ng-show="_.isEmpty(setting.properties) && _.isEmpty(setting.dependencies) && !valueform.$visible" class="col-md-12 gray40-message">
<em>Currently there are no properties and dependencies set for this interpreter</em>
</div>
</div>
<div class="row interpreter">
<div class="col-md-12" ng-show="!_.isEmpty(setting.properties) || valueform.$visible">
<h5>Properties</h5>
<table class="table table-striped">
@ -285,8 +286,8 @@ limitations under the License.
<thead>
<tr>
<th style="width:40%">artifact</th>
<th>exclude</th>
<th ng-if="valueform.$visible">action</th>
<th style="width:40%">exclude</th>
<th style="width:20%" ng-if="valueform.$visible">action</th>
</tr>
</thead>
<tr ng-repeat="dep in setting.dependencies">

View file

@ -15,13 +15,14 @@
angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window,
$routeParams, $location, $timeout, $compile,
$http, websocketMsgSrv, baseUrlSrv, ngToast,
$http, $q, websocketMsgSrv, baseUrlSrv, ngToast,
saveAsService, esriLoader) {
var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
$scope.parentNote = null;
$scope.paragraph = null;
$scope.originalText = '';
$scope.editor = null;
$scope.magic = null;
var paragraphScope = $rootScope.$new(true, $rootScope);
@ -78,15 +79,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
var angularObjectRegistry = {};
var editorModes = {
'ace/mode/python': /^%(\w*\.)?(pyspark|python)\s*$/,
'ace/mode/scala': /^%(\w*\.)?spark\s*$/,
'ace/mode/r': /^%(\w*\.)?(r|sparkr|knitr)\s*$/,
'ace/mode/sql': /^%(\w*\.)?\wql/,
'ace/mode/markdown': /^%md/,
'ace/mode/sh': /^%sh/
};
// Controller init
$scope.init = function(newParagraph, note) {
$scope.paragraph = newParagraph;
@ -538,7 +530,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
$scope.aceChanged = function() {
$scope.dirtyText = $scope.editor.getSession().getValue();
$scope.startSaveTimer();
$scope.setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition());
setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition());
};
$scope.aceLoaded = function(_editor) {
@ -576,37 +568,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
// not applying emacs key binding while the binding override Ctrl-v. default behavior of paste text on windows.
}
$scope.setParagraphMode = function(session, paragraphText, pos) {
// Evaluate the mode only if the first 30 characters of the paragraph have been modified or the the position is undefined.
if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30)) {
// If paragraph loading, use config value if exists
if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) {
session.setMode($scope.paragraph.config.editorMode);
} else {
// Defaults to spark mode
var newMode = 'ace/mode/scala';
// Test first against current mode
var oldMode = session.getMode().$id;
if (!editorModes[oldMode] || !editorModes[oldMode].test(paragraphText)) {
for (var key in editorModes) {
if (key !== oldMode) {
if (editorModes[key].test(paragraphText)) {
$scope.paragraph.config.editorMode = key;
session.setMode(key);
return true;
}
}
}
$scope.paragraph.config.editorMode = newMode;
session.setMode(newMode);
}
}
}
};
var remoteCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (!$scope.editor.isFocused()) { return;}
if (!$scope.editor.isFocused()) {
return;
}
pos = session.getTextRange(new Range(0, 0, pos.row, pos.column)).length;
var buf = session.getValue();
@ -662,7 +628,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
autoAdjustEditorHeight(_editor.container.id);
});
$scope.setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue());
setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue());
// autocomplete on '.'
/*
@ -737,6 +703,53 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
}
};
var getAndSetEditorSetting = function(session, interpreterName) {
var deferred = $q.defer();
websocketMsgSrv.getEditorSetting(interpreterName);
$timeout(
$scope.$on('editorSetting', function(event, data) {
deferred.resolve(data);
}
), 1000);
deferred.promise.then(function(editorSetting) {
if (!_.isEmpty(editorSetting.editor)) {
var mode = 'ace/mode/' + editorSetting.editor.language;
$scope.paragraph.config.editorMode = mode;
session.setMode(mode);
}
});
};
var setParagraphMode = function(session, paragraphText, pos) {
// Evaluate the mode only if the the position is undefined
// or the first 30 characters of the paragraph have been modified
// or cursor position is at beginning of second line.(in case user hit enter after typing %magic)
if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30) || (pos.row === 1 && pos.column === 0)) {
// If paragraph loading, use config value if exists
if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) {
session.setMode($scope.paragraph.config.editorMode);
} else {
var magic;
// set editor mode to default interpreter syntax if paragraph text doesn't start with '%'
// TODO(mina): dig into the cause what makes interpreterBindings has no element
if (!paragraphText.startsWith('%') && ((typeof pos !== 'undefined') && pos.row === 0 && pos.column === 1) ||
(typeof pos === 'undefined') && $scope.$parent.interpreterBindings.length !== 0) {
magic = $scope.$parent.interpreterBindings[0].name;
getAndSetEditorSetting(session, magic);
} else {
var replNameRegexp = /%(.+?)\s/g;
var match = replNameRegexp.exec(paragraphText);
if (match && $scope.magic !== match[1]) {
magic = match[1].trim();
$scope.magic = magic;
getAndSetEditorSetting(session, magic);
}
}
}
}
};
var autoAdjustEditorHeight = function(id) {
var editor = $scope.editor;
var height = editor.getSession().getScreenLength() * editor.renderer.lineHeight +
@ -2259,7 +2272,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
var noteId = $route.current.pathParams.noteId;
$http.get(baseUrlSrv.getRestApiBase() + '/helium/suggest/' + noteId + '/' + $scope.paragraph.id)
.success(function(data, status, headers, config) {
console.log('Suggested apps %o', data);
$scope.suggestion = data.body;
})
.error(function(err, status, headers, config) {

View file

@ -76,8 +76,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents',
action: function(dialog) {
dialog.close();
angular.element('#loginModal').modal({
show: 'true'
});
show: 'true'
});
}
}, {
label: 'Cancel',
@ -97,6 +97,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents',
$rootScope.$broadcast('updateProgress', data);
} else if (op === 'COMPLETION_LIST') {
$rootScope.$broadcast('completionList', data);
} else if (op === 'EDITOR_SETTING') {
$rootScope.$broadcast('editorSetting', data);
} else if (op === 'ANGULAR_OBJECT_UPDATE') {
$rootScope.$broadcast('angularObjectUpdate', data);
} else if (op === 'ANGULAR_OBJECT_REMOVE') {

View file

@ -180,6 +180,15 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope,
});
},
getEditorSetting: function(replName) {
websocketEvents.sendNewEvent({
op: 'EDITOR_SETTING',
data: {
magic: replName
}
});
},
isConnected: function() {
return websocketEvents.isConnected();
},

View file

@ -46,6 +46,7 @@ limitations under the License.
<link rel="stylesheet" href="bower_components/bootstrap3-dialog/dist/css/bootstrap-dialog.min.css" />
<link rel="stylesheet" href="bower_components/pikaday/css/pikaday.css" />
<link rel="stylesheet" href="bower_components/handsontable/dist/handsontable.css" />
<link rel="stylesheet" href="bower_components/github-markdown-css/github-markdown.css" />
<!-- endbower -->
<link rel="stylesheet" href="bower_components/jquery-ui/themes/base/jquery-ui.css" />
<link rel="stylesheet" href="bower_components/select2/dist/css/select2.css" />

View file

@ -27,6 +27,7 @@ import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.zeppelin.notebook.repo.VFSNotebookRepo;
import org.apache.zeppelin.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -102,6 +103,11 @@ public class ZeppelinConfiguration extends XMLConfiguration {
conf = new ZeppelinConfiguration();
}
}
LOG.info("Server Host: " + conf.getServerAddress());
LOG.info("Server Port: " + conf.getServerPort());
LOG.info("Context Path: " + conf.getServerContextPath());
LOG.info("Zeppelin Version: " + Util.getVersion());
return conf;
}

View file

@ -49,6 +49,8 @@ import java.util.Properties;
import java.util.Set;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
@ -195,10 +197,10 @@ public class InterpreterFactory implements InterpreterGroupFactory {
Set<String> interpreterKeys = Interpreter.registeredInterpreters.keySet();
for (String interpreterKey : interpreterKeys) {
if (className
.equals(Interpreter.registeredInterpreters.get(interpreterKey)
.getClassName())) {
.equals(Interpreter.registeredInterpreters.get(interpreterKey)
.getClassName())) {
Interpreter.registeredInterpreters.get(interpreterKey)
.setPath(interpreterDirString);
.setPath(interpreterDirString);
logger.info("Interpreter " + interpreterKey + " found. class=" + className);
cleanCl.put(interpreterDirString, ccl);
}
@ -223,7 +225,8 @@ public class InterpreterFactory implements InterpreterGroupFactory {
InterpreterInfo interpreterInfo;
for (RegisteredInterpreter r : Interpreter.registeredInterpreters.values()) {
interpreterInfo =
new InterpreterInfo(r.getClassName(), r.getName(), r.isDefaultInterpreter());
new InterpreterInfo(r.getClassName(), r.getName(), r.isDefaultInterpreter(),
r.getEditor());
add(r.getGroup(), interpreterInfo, convertInterpreterProperties(r.getProperties()),
r.getPath());
}
@ -335,7 +338,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
for (RegisteredInterpreter registeredInterpreter : registeredInterpreters) {
InterpreterInfo interpreterInfo =
new InterpreterInfo(registeredInterpreter.getClassName(), registeredInterpreter.getName(),
registeredInterpreter.isDefaultInterpreter());
registeredInterpreter.isDefaultInterpreter(), registeredInterpreter.getEditor());
Properties properties = new Properties();
Map<String, InterpreterProperty> p = registeredInterpreter.getProperties();
@ -370,10 +373,11 @@ public class InterpreterFactory implements InterpreterGroupFactory {
fis.close();
String json = sb.toString();
InterpreterInfoSaving info = gson.fromJson(json, InterpreterInfoSaving.class);
InterpreterInfoSaving infoSaving = gson.fromJson(json, InterpreterInfoSaving.class);
for (String k : info.interpreterSettings.keySet()) {
InterpreterSetting setting = info.interpreterSettings.get(k);
for (String k : infoSaving.interpreterSettings.keySet()) {
InterpreterSetting setting = infoSaving.interpreterSettings.get(k);
List<InterpreterInfo> infos = setting.getInterpreterInfos();
// Always use separate interpreter process
// While we decided to turn this feature on always (without providing
@ -391,15 +395,23 @@ public class InterpreterFactory implements InterpreterGroupFactory {
depClassPath = interpreterSettingObject.getPath();
setting.setPath(depClassPath);
for (InterpreterInfo info : infos) {
if (info.getEditor() == null) {
Map<String, Object> editor = getEditorFromSettingByClassName(interpreterSettingObject,
info.getClassName());
info.setEditor(editor);
}
}
setting.setInterpreterGroupFactory(this);
loadInterpreterDependencies(setting);
interpreterSettings.put(k, setting);
}
this.interpreterBindings = info.interpreterBindings;
this.interpreterBindings = infoSaving.interpreterBindings;
if (info.interpreterRepositories != null) {
for (RemoteRepository repo : info.interpreterRepositories) {
if (infoSaving.interpreterRepositories != null) {
for (RemoteRepository repo : infoSaving.interpreterRepositories) {
if (!depResolver.getRepos().contains(repo)) {
this.interpreterRepositories.add(repo);
}
@ -407,8 +419,18 @@ public class InterpreterFactory implements InterpreterGroupFactory {
}
}
private void loadInterpreterDependencies(final InterpreterSetting setting) {
public Map<String, Object> getEditorFromSettingByClassName(InterpreterSetting intpSetting,
String className) {
List<InterpreterInfo> intpInfos = intpSetting.getInterpreterInfos();
for (InterpreterInfo intpInfo : intpInfos) {
if (className.equals(intpInfo.getClassName())) {
return intpInfo.getEditor();
}
}
return ImmutableMap.of("language", (Object) "text");
}
private void loadInterpreterDependencies(final InterpreterSetting setting) {
setting.setStatus(InterpreterSetting.Status.DOWNLOADING_DEPENDENCIES);
interpreterSettings.put(setting.getId(), setting);
synchronized (interpreterSettings) {
@ -454,6 +476,46 @@ public class InterpreterFactory implements InterpreterGroupFactory {
}
}
/**
* Overwrite dependency jar under local-repo/{interpreterId}
* if jar file in original path is changed
*/
private void copyDependenciesFromLocalPath(final InterpreterSetting setting) {
setting.setStatus(InterpreterSetting.Status.DOWNLOADING_DEPENDENCIES);
interpreterSettings.put(setting.getId(), setting);
synchronized (interpreterSettings) {
final Thread t = new Thread() {
public void run() {
try {
List<Dependency> deps = setting.getDependencies();
if (deps != null) {
for (Dependency d : deps) {
File destDir = new File(conf.getRelativeDir(ConfVars.ZEPPELIN_DEP_LOCALREPO));
int numSplits = d.getGroupArtifactVersion().split(":").length;
if (!(numSplits >= 3 && numSplits <= 6)) {
depResolver.copyLocalDependency(d.getGroupArtifactVersion(),
new File(destDir, setting.getId()));
}
}
}
setting.setStatus(InterpreterSetting.Status.READY);
} catch (Exception e) {
logger.error(String.format("Error while copying deps for interpreter group : %s," +
" go to interpreter setting page click on edit and save it again to make " +
"this interpreter work properly.",
setting.getGroup()), e);
setting.setErrorReason(e.getLocalizedMessage());
setting.setStatus(InterpreterSetting.Status.ERROR);
} finally {
interpreterSettings.put(setting.getId(), setting);
}
}
};
t.start();
}
}
private void saveToFile() throws IOException {
String jsonString;
@ -853,17 +915,21 @@ public class InterpreterFactory implements InterpreterGroupFactory {
synchronized (interpreterSettings) {
InterpreterSetting intpsetting = interpreterSettings.get(id);
if (intpsetting != null) {
try {
stopJobAllInterpreter(intpsetting);
stopJobAllInterpreter(intpsetting);
intpsetting.closeAndRmoveAllInterpreterGroups();
intpsetting.setOption(option);
intpsetting.setProperties(properties);
intpsetting.setDependencies(dependencies);
loadInterpreterDependencies(intpsetting);
intpsetting.closeAndRmoveAllInterpreterGroups();
intpsetting.setOption(option);
intpsetting.setProperties(properties);
intpsetting.setDependencies(dependencies);
loadInterpreterDependencies(intpsetting);
saveToFile();
saveToFile();
} catch (Exception e) {
throw e;
} finally {
loadFromFile();
}
} else {
throw new InterpreterException("Interpreter setting id " + id + " not found");
}
@ -892,6 +958,9 @@ public class InterpreterFactory implements InterpreterGroupFactory {
public void restart(String id) {
synchronized (interpreterSettings) {
InterpreterSetting intpsetting = interpreterSettings.get(id);
// Check if dependency in specified path is changed
// If it did, overwrite old dependency jar with new one
copyDependenciesFromLocalPath(intpsetting);
if (intpsetting != null) {
stopJobAllInterpreter(intpsetting);
@ -1276,6 +1345,35 @@ public class InterpreterFactory implements InterpreterGroupFactory {
this.env = env;
}
public Map<String, Object> getEditorSetting(String noteId, String replName) {
Interpreter intp = getInterpreter(noteId, replName);
Map<String, Object> editor = Maps.newHashMap(
ImmutableMap.<String, Object>builder()
.put("language", "text").build());
String defaultSettingName = getDefaultInterpreterSetting(noteId).getName();
String group = StringUtils.EMPTY;
try {
List<InterpreterSetting> intpSettings = getInterpreterSettings(noteId);
for (InterpreterSetting intpSetting : intpSettings) {
String[] replNameSplit = replName.split("\\.");
if (replNameSplit.length == 2) {
group = replNameSplit[0];
}
// when replName is 'name' of interpreter
if (defaultSettingName.equals(intpSetting.getName())) {
editor = getEditorFromSettingByClassName(intpSetting, intp.getClassName());
}
// when replName is 'alias name' of interpreter or 'group' of interpreter
if (replName.equals(intpSetting.getName()) || group.equals(intpSetting.getName())) {
editor = getEditorFromSettingByClassName(intpSetting, intp.getClassName());
break;
}
}
} catch (NullPointerException e) {
logger.warn("Couldn't get interpreter editor language");
}
return editor;
}
private Interpreter getDevInterpreter() {
if (devInterpreter == null) {

View file

@ -19,6 +19,8 @@ package org.apache.zeppelin.interpreter;
import com.google.gson.annotations.SerializedName;
import java.util.Map;
/**
* Information of interpreters in this interpreter setting.
* this will be serialized for conf/interpreter.json and REST api response.
@ -27,11 +29,14 @@ public class InterpreterInfo {
private String name;
@SerializedName("class") private String className;
private boolean defaultInterpreter = false;
private Map<String, Object> editor;
InterpreterInfo(String className, String name, boolean defaultInterpreter) {
InterpreterInfo(String className, String name, boolean defaultInterpreter,
Map<String, Object> editor) {
this.className = className;
this.name = name;
this.defaultInterpreter = defaultInterpreter;
this.editor = editor;
}
public String getName() {
@ -50,6 +55,14 @@ public class InterpreterInfo {
return defaultInterpreter;
}
public Map<String, Object> getEditor() {
return editor;
}
public void setEditor(Map<String, Object> editor) {
this.editor = editor;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof InterpreterInfo)) {

View file

@ -21,43 +21,43 @@ import java.util.HashMap;
import java.util.Map;
/**
* Zeppelin websocker massage template class.
* Zeppelin websocket massage template class.
*/
public class Message {
/**
* Representation of event type.
*/
public static enum OP {
GET_HOME_NOTE, // [c-s] load note for home screen
GET_HOME_NOTE, // [c-s] load note for home screen
GET_NOTE, // [c-s] client load note
// @param id note id
GET_NOTE, // [c-s] client load note
// @param id note id
NOTE, // [s-c] note info
// @param note serlialized Note object
NOTE, // [s-c] note info
// @param note serlialized Note object
PARAGRAPH, // [s-c] paragraph info
// @param paragraph serialized paragraph object
PARAGRAPH, // [s-c] paragraph info
// @param paragraph serialized paragraph object
PROGRESS, // [s-c] progress update
// @param id paragraph id
// @param progress percentage progress
PROGRESS, // [s-c] progress update
// @param id paragraph id
// @param progress percentage progress
NEW_NOTE, // [c-s] create new notebook
DEL_NOTE, // [c-s] delete notebook
// @param id note id
CLONE_NOTE, // [c-s] clone new notebook
// @param id id of note to clone
// @param name name fpor the cloned note
IMPORT_NOTE, // [c-s] import notebook
// @param object notebook
NEW_NOTE, // [c-s] create new notebook
DEL_NOTE, // [c-s] delete notebook
// @param id note id
CLONE_NOTE, // [c-s] clone new notebook
// @param id id of note to clone
// @param name name fpor the cloned note
IMPORT_NOTE, // [c-s] import notebook
// @param object notebook
NOTE_UPDATE,
RUN_PARAGRAPH, // [c-s] run paragraph
// @param id paragraph id
// @param paragraph paragraph content.ie. script
// @param config paragraph config
// @param params paragraph params
RUN_PARAGRAPH, // [c-s] run paragraph
// @param id paragraph id
// @param paragraph paragraph content.ie. script
// @param config paragraph config
// @param params paragraph params
COMMIT_PARAGRAPH, // [c-s] commit paragraph
// @param id paragraph id
@ -69,72 +69,77 @@ public class Message {
CANCEL_PARAGRAPH, // [c-s] cancel paragraph run
// @param id paragraph id
MOVE_PARAGRAPH, // [c-s] move paragraph order
// @param id paragraph id
// @param index index the paragraph want to go
MOVE_PARAGRAPH, // [c-s] move paragraph order
// @param id paragraph id
// @param index index the paragraph want to go
INSERT_PARAGRAPH, // [c-s] create new paragraph below current paragraph
// @param target index
COMPLETION, // [c-s] ask completion candidates
// @param id
// @param buf current code
// @param cursor cursor position in code
EDITOR_SETTING, // [c-s] ask paragraph editor setting
// @param magic magic keyword written in paragraph
// ex) spark.spark or spark
COMPLETION_LIST, // [s-c] send back completion candidates list
// @param id
// @param completions list of string
COMPLETION, // [c-s] ask completion candidates
// @param id
// @param buf current code
// @param cursor cursor position in code
LIST_NOTES, // [c-s] ask list of note
RELOAD_NOTES_FROM_REPO, // [c-s] reload notes from repo
COMPLETION_LIST, // [s-c] send back completion candidates list
// @param id
// @param completions list of string
NOTES_INFO, // [s-c] list of note infos
// @param notes serialized List<NoteInfo> object
LIST_NOTES, // [c-s] ask list of note
RELOAD_NOTES_FROM_REPO, // [c-s] reload notes from repo
NOTES_INFO, // [s-c] list of note infos
// @param notes serialized List<NoteInfo> object
PARAGRAPH_REMOVE,
PARAGRAPH_CLEAR_OUTPUT,
PARAGRAPH_APPEND_OUTPUT, // [s-c] append output
PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output
PARAGRAPH_APPEND_OUTPUT, // [s-c] append output
PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output
PING,
AUTH_INFO,
ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object
ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del
ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object
ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del
ANGULAR_OBJECT_UPDATED, // [c-s] angular object value updated,
ANGULAR_OBJECT_UPDATED, // [c-s] angular object value updated,
ANGULAR_OBJECT_CLIENT_BIND, // [c-s] angular object updated from AngularJS z object
ANGULAR_OBJECT_CLIENT_BIND, // [c-s] angular object updated from AngularJS z object
ANGULAR_OBJECT_CLIENT_UNBIND, // [c-s] angular object unbind from AngularJS z object
ANGULAR_OBJECT_CLIENT_UNBIND, // [c-s] angular object unbind from AngularJS z object
LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations
CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations
// @param settings serialized Map<String, String> object
LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations
CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations
// @param settings serialized Map<String, String> object
CHECKPOINT_NOTEBOOK, // [c-s] checkpoint notebook to storage repository
// @param noteId
// @param checkpointName
CHECKPOINT_NOTEBOOK, // [c-s] checkpoint notebook to storage repository
// @param noteId
// @param checkpointName
LIST_REVISION_HISTORY, // [c-s] list revision history of the notebook
// @param noteId
NOTE_REVISION, // [c-s] get certain revision of note
// @param noteId
// @param revisionId
LIST_REVISION_HISTORY, // [c-s] list revision history of the notebook
// @param noteId
NOTE_REVISION, // [c-s] get certain revision of note
// @param noteId
// @param revisionId
APP_APPEND_OUTPUT, // [s-c] append output
APP_UPDATE_OUTPUT, // [s-c] update (replace) output
APP_LOAD, // [s-c] on app load
APP_STATUS_CHANGE, // [s-c] on app status change
APP_APPEND_OUTPUT, // [s-c] append output
APP_UPDATE_OUTPUT, // [s-c] update (replace) output
APP_LOAD, // [s-c] on app load
APP_STATUS_CHANGE, // [s-c] on app status change
LIST_NOTEBOOK_JOBS, // [c-s] get notebook job management infomations
LIST_UPDATE_NOTEBOOK_JOBS, // [s-c] get job management informations
LIST_NOTEBOOK_JOBS, // [c-s] get notebook job management infomations
LIST_UPDATE_NOTEBOOK_JOBS, // [c-s] get job management informations for until unixtime
UNSUBSCRIBE_UPDATE_NOTEBOOK_JOBS, // [c-s] unsubscribe job information for job management
GET_INTERPRETER_BINDINGS, // [c-s] get interpreter bindings
// @param noteID
SAVE_INTERPRETER_BINDINGS, // [c-s] save interpreter bindings
// @param noteID
// @param selectedSettingIds
INTERPRETER_BINDINGS // [s-c] interpreter bindings
// @param unixTime
GET_INTERPRETER_BINDINGS, // [c-s] get interpreter bindings
// @param noteID
SAVE_INTERPRETER_BINDINGS, // [c-s] save interpreter bindings
// @param noteID
// @param selectedSettingIds
INTERPRETER_BINDINGS // [s-c] interpreter bindings
}
public OP op;

View file

@ -34,12 +34,22 @@ import org.apache.zeppelin.dep.DependencyResolver;
import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
import org.apache.zeppelin.interpreter.mock.MockInterpreter2;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreter;
import org.apache.zeppelin.notebook.JobListenerFactory;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.VFSNotebookRepo;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.SearchService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.quartz.SchedulerException;
import org.sonatype.aether.RepositoryException;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import org.mockito.Mock;
public class InterpreterFactoryTest {
@ -47,13 +57,19 @@ public class InterpreterFactoryTest {
private File tmpDir;
private ZeppelinConfiguration conf;
private InterpreterContext context;
private Notebook notebook;
private NotebookRepo notebookRepo;
private DependencyResolver depResolver;
private SchedulerFactory schedulerFactory;
@Mock
private JobListenerFactory jobListenerFactory;
@Before
public void setUp() throws Exception {
tmpDir = new File(System.getProperty("java.io.tmpdir")+"/ZeppelinLTest_"+System.currentTimeMillis());
tmpDir.mkdirs();
new File(tmpDir, "conf").mkdirs();
FileUtils.copyDirectory(new File("src/test/resources/interpreter"), new File(tmpDir, "interpreter"));
Map<String, InterpreterProperty> propertiesMockInterpreter1 = new HashMap<String, InterpreterProperty>();
propertiesMockInterpreter1.put("PROPERTY_1", new InterpreterProperty("PROPERTY_1", "", "VALUE_1", "desc"));
@ -62,11 +78,22 @@ public class InterpreterFactoryTest {
MockInterpreter2.register("mock2", "org.apache.zeppelin.interpreter.mock.MockInterpreter2");
System.setProperty(ConfVars.ZEPPELIN_HOME.getVarName(), tmpDir.getAbsolutePath());
System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1,org.apache.zeppelin.interpreter.mock.MockInterpreter2");
System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(),
"org.apache.zeppelin.interpreter.mock.MockInterpreter1," +
"org.apache.zeppelin.interpreter.mock.MockInterpreter2," +
"org.apache.zeppelin.interpreter.mock.MockInterpreter11");
System.setProperty(ConfVars.ZEPPELIN_INTERPRETER_GROUP_ORDER.getVarName(),
"mock1,mock2,mock11,dev");
conf = new ZeppelinConfiguration();
schedulerFactory = new SchedulerFactory();
depResolver = new DependencyResolver(tmpDir.getAbsolutePath() + "/local-repo");
factory = new InterpreterFactory(conf, new InterpreterOption(false), null, null, null, depResolver);
context = new InterpreterContext("note", "id", "title", "text", null, null, null, null, null, null, null);
SearchService search = mock(SearchService.class);
notebookRepo = new VFSNotebookRepo(conf);
notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, jobListenerFactory, search,
null, null);
}
@After
@ -128,7 +155,7 @@ public class InterpreterFactoryTest {
public void testFactoryDefaultList() throws IOException, RepositoryException {
// get default settings
List<String> all = factory.getDefaultInterpreterSettingList();
assertTrue(factory.getRegisteredInterpreterList().size() >= all.size());
assertTrue(factory.get().size() >= all.size());
}
@Test
@ -166,8 +193,8 @@ public class InterpreterFactoryTest {
@Test
public void testInterpreterAliases() throws IOException, RepositoryException {
factory = new InterpreterFactory(conf, null, null, null, depResolver);
final InterpreterInfo info1 = new InterpreterInfo("className1", "name1", true);
final InterpreterInfo info2 = new InterpreterInfo("className2", "name1", true);
final InterpreterInfo info1 = new InterpreterInfo("className1", "name1", true, null);
final InterpreterInfo info2 = new InterpreterInfo("className2", "name1", true, null);
factory.add("group1", new ArrayList<InterpreterInfo>(){{
add(info1);
}}, new ArrayList<Dependency>(), new InterpreterOption(true), new Properties(), "/path1");
@ -196,4 +223,29 @@ public class InterpreterFactoryTest {
assertEquals("'.' is invalid for InterpreterSetting name.", e.getMessage());
}
}
@Test
public void getEditorSetting() throws IOException, RepositoryException, SchedulerException {
List<String> intpIds = new ArrayList<>();
for(InterpreterSetting intpSetting: factory.get()) {
if (intpSetting.getName().startsWith("mock1")) {
intpIds.add(intpSetting.getId());
}
}
Note note = notebook.createNote(intpIds, null);
// get editor setting from interpreter-setting.json
Map<String, Object> editor = factory.getEditorSetting(note.getId(), "mock11");
assertEquals("java", editor.get("language"));
// when interpreter is not loaded via interpreter-setting.json
// or editor setting doesn't exit
editor = factory.getEditorSetting(note.getId(), "mock1");
assertEquals(null, editor.get("language"));
// when interpreter is not bound to note
editor = factory.getEditorSetting(note.getId(), "mock2");
assertEquals("text", editor.get("language"));
}
}

View file

@ -19,7 +19,6 @@ package org.apache.zeppelin.notebook;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterFactory;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.search.SearchService;
@ -28,7 +27,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.*;

View file

@ -75,6 +75,7 @@ public class NotebookTest implements JobListenerFactory{
notebookDir = new File(tmpDir + "/notebook");
notebookDir.mkdirs();
System.setProperty(ConfVars.ZEPPELIN_CONF_DIR.getVarName(), tmpDir.toString() + "/conf");
System.setProperty(ConfVars.ZEPPELIN_HOME.getVarName(), tmpDir.getAbsolutePath());
System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(), notebookDir.getAbsolutePath());
System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1,org.apache.zeppelin.interpreter.mock.MockInterpreter2");
@ -95,8 +96,7 @@ public class NotebookTest implements JobListenerFactory{
credentials = new Credentials(conf.credentialsPersist(), conf.getCredentialsPath());
notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search,
notebookAuthorization, credentials);
notebookAuthorization, credentials);
}
@After
@ -109,7 +109,7 @@ public class NotebookTest implements JobListenerFactory{
Note note = notebook.createNote(null);
factory.setInterpreters(note.getId(), factory.getDefaultInterpreterSettingList());
// run with defatul repl
// run with default repl
Paragraph p1 = note.addParagraph();
Map config = p1.getConfig();
config.put("enabled", true);
@ -232,7 +232,7 @@ public class NotebookTest implements JobListenerFactory{
p1.setText("hello world");
note.run(p1.getId());
while(p1.isTerminated()==false || p1.getResult()==null) Thread.yield();
while(p1.isTerminated() == false || p1.getResult() == null) Thread.yield();
assertEquals("repl1: hello world", p1.getResult().message());
// clear paragraph output/result
@ -267,7 +267,7 @@ public class NotebookTest implements JobListenerFactory{
note.runAll();
// wait for finish
while(p3.isTerminated()==false) {
while(p3.isTerminated() == false) {
Thread.yield();
}
@ -279,7 +279,7 @@ public class NotebookTest implements JobListenerFactory{
}
@Test
public void testSchedule() throws InterruptedException, IOException{
public void testSchedule() throws InterruptedException, IOException {
// create a note and a paragraph
Note note = notebook.createNote(null);
factory.setInterpreters(note.getId(), factory.getDefaultInterpreterSettingList());
@ -297,7 +297,7 @@ public class NotebookTest implements JobListenerFactory{
config.put("cron", "* * * * * ?");
note.setConfig(config);
notebook.refreshCron(note.getId());
Thread.sleep(1*1000);
Thread.sleep(1 * 1000);
// remove cron scheduler.
config.put("cron", null);
@ -306,7 +306,7 @@ public class NotebookTest implements JobListenerFactory{
Thread.sleep(1000);
dateFinished = p.getDateFinished();
assertNotNull(dateFinished);
Thread.sleep(1*1000);
Thread.sleep(1 * 1000);
assertEquals(dateFinished, p.getDateFinished());
}
@ -476,8 +476,8 @@ public class NotebookTest implements JobListenerFactory{
p2.setText("%mock2 world");
note.runAll();
while(p1.isTerminated()==false || p1.getResult()==null) Thread.yield();
while(p2.isTerminated()==false || p2.getResult()==null) Thread.yield();
while (p1.isTerminated() == false || p1.getResult() == null) Thread.yield();
while (p2.isTerminated() == false || p2.getResult() == null) Thread.yield();
assertEquals(2, ResourcePoolUtils.getAllResources().size());
@ -604,26 +604,26 @@ public class NotebookTest implements JobListenerFactory{
new HashSet<String>(Arrays.asList("user1")));
assertEquals(notebookAuthorization.isOwner(note.getId(),
new HashSet<String>(Arrays.asList("user2"))), false);
new HashSet<String>(Arrays.asList("user2"))), false);
assertEquals(notebookAuthorization.isOwner(note.getId(),
new HashSet<String>(Arrays.asList("user1"))), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<String>(Arrays.asList("user3"))), false);
new HashSet<String>(Arrays.asList("user3"))), false);
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<String>(Arrays.asList("user2"))), true);
new HashSet<String>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isWriter(note.getId(),
new HashSet<String>(Arrays.asList("user2"))), false);
new HashSet<String>(Arrays.asList("user2"))), false);
assertEquals(notebookAuthorization.isWriter(note.getId(),
new HashSet<String>(Arrays.asList("user1"))), true);
new HashSet<String>(Arrays.asList("user1"))), true);
// Test clearing of permssions
notebookAuthorization.setReaders(note.getId(), Sets.<String>newHashSet());
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<String>(Arrays.asList("user2"))), true);
new HashSet<String>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isReader(note.getId(),
new HashSet<String>(Arrays.asList("user3"))), true);
new HashSet<String>(Arrays.asList("user3"))), true);
notebook.removeNote(note.getId(), null);
}

View file

@ -0,0 +1,12 @@
[
{
"group": "mock11",
"name": "mock11",
"className": "org.apache.zeppelin.interpreter.mock.MockInterpreter11",
"properties": {
},
"editor": {
"language": "java"
}
}
]