mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge branch 'master' into ZEPPELIN-1306
This commit is contained in:
commit
322d427f26
35 changed files with 865 additions and 177 deletions
|
|
@ -1,4 +1,4 @@
|
|||
#Zeppelin
|
||||
# Apache Zeppelin
|
||||
|
||||
**Documentation:** [User Guide](http://zeppelin.apache.org/docs/latest/index.html)<br/>
|
||||
**Mailing Lists:** [User and Dev mailing list](http://zeppelin.apache.org/community.html)<br/>
|
||||
|
|
|
|||
94
docs/CONTRIBUTING.md
Normal file
94
docs/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Contributing to Apache Zeppelin Documentation
|
||||
|
||||
## Folder Structure
|
||||
`docs/` folder is organized as below:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── _includes/themes/zeppelin
|
||||
│ ├── _navigation.html
|
||||
│ └── default.html
|
||||
├── _layouts
|
||||
├── _plugins
|
||||
├── assets/themes/zeppelin -> {ASSET_PATH}
|
||||
│ ├── bootstrap
|
||||
│ ├── css
|
||||
│ ├── img
|
||||
│ └── js
|
||||
├── development/ *.md
|
||||
├── displaysystem/ *.md
|
||||
├── install/ *.md
|
||||
├── interpreter/ *.md
|
||||
├── manual/ *.md
|
||||
├── quickstart/ *.md
|
||||
├── rest-api/ *.md
|
||||
├── security/ *.md
|
||||
├── storage/ *.md
|
||||
├── Gemfile
|
||||
├── Gemfile.lock
|
||||
├── _config.yml
|
||||
├── index.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
- `_navigation.html`: the dropdown menu in navbar
|
||||
- `default.html` & `_layouts/`: define default HTML layout
|
||||
- `_plugins/`: custom plugin `*.rb` files can be placed in this folder. See [jekyll/plugins](https://jekyllrb.com/docs/plugins/) for the further information.
|
||||
- `{ASSET_PATH}/css/style.css`: extra css components can be defined
|
||||
- `{ASSET_PATH}/img/docs-img/`: image files used for document pages can be placed in this folder
|
||||
- `{ASSET_PATH}/js/`: extra `.js` files can be placed
|
||||
- `Gemfile`: defines bundle dependencies. They will be installed by `bundle install`.
|
||||
- `Gemfile.lock`: when you run `bundle install`, bundler will persist all gems name and their version to this file. For the more details, see [Bundle "The Gemfile Lock"](http://bundler.io/v1.10/man/bundle-install.1.html#THE-GEMFILE-LOCK)
|
||||
- `documentation_group`: `development/`, `displaysystem/`, `install/`, `interpreter/`...
|
||||
- `_config.yml`: defines configuration options for docs website. See [jekyll/configuration](https://jekyllrb.com/docs/configuration/) for the other available config variables.
|
||||
- `index.md`: the main page of `http://zeppelin.apache.org/docs/<ZEPPELIN_VERSION>/`
|
||||
|
||||
|
||||
## Markdown
|
||||
Zeppelin documentation pages are written with [Markdown](http://daringfireball.net/projects/markdown/). It is possible to use [GitHub flavored syntax](https://help.github.com/categories/writing-on-github/) and intermix plain HTML.
|
||||
|
||||
## Front matter
|
||||
Every page contains [YAML front matter](https://jekyllrb.com/docs/frontmatter/) block in their header. Don't forget to wrap the front matter list with triple-dashed lines(`---`) like below.
|
||||
The document page should start this triple-dashed lines. Or you will face 404 error, since Jekyll can't find the page.
|
||||
|
||||
```
|
||||
---
|
||||
layout: page
|
||||
title: "Apache Zeppelin Tutorial"
|
||||
description: "This tutorial page contains a short walk-through tutorial that uses Apache Spark backend. Please note that this tutorial is valid for Spark 1.3 and higher."
|
||||
group: quickstart
|
||||
---
|
||||
```
|
||||
|
||||
- `layout`: the default layout is `page` which is defined in `_layout/page.html`.
|
||||
- `title`: the title for the document. Please note that if it needs to include `Zeppelin`, it should be `Apache Zeppelin`, not `Zeppelin`.
|
||||
- `description`: a short description for the document. One or two sentences would be enough. This description also will be shown as an extract sentence when people search pages.
|
||||
- `group`: a category of the document page
|
||||
|
||||
## Headings
|
||||
All documents are structured with headings. From these headings, you can automatically generate a **Table of Contents**. There is a simple rule for Zeppelin docs headings.
|
||||
|
||||
```
|
||||
# Level-1 heading <- used only for the main title
|
||||
## Level-2 heading <- start with this
|
||||
### Level-3 heading
|
||||
#### Level-4 heading <- won't be converted in TOC from this level
|
||||
```
|
||||
|
||||
## Table of contents(TOC)
|
||||
|
||||
```
|
||||
<div id="toc"></div>
|
||||
```
|
||||
|
||||
Add this line below `# main title` in order to generate a **Table of Contents**. Headings until `### (Level-3 heading)` are included to TOC.
|
||||
|
||||
|
||||
Default setting options for TOC are definded in [here](https://github.com/apache/zeppelin/blob/master/docs/assets/themes/zeppelin/js/toc.js#L4).
|
||||
|
||||
|
||||
## Adding new pages
|
||||
If you're going to create new pages, there are some spots you need to add the location of the page.
|
||||
|
||||
- **Dropdown menu in navbar**: add your docs location to [_navigation.html](https://github.com/apache/zeppelin/blob/master/docs/_includes/themes/zeppelin/_navigation.html)
|
||||
- **Main index**: add your docs below [What is the next?](http://zeppelin.apache.org/docs/latest/#what-is-the-next) section in [index.md](https://github.com/apache/zeppelin/blob/master/docs/index.md) with a short description. No need to do this if the page is for **Interpreters**.
|
||||
|
|
@ -1,41 +1,55 @@
|
|||
## Apache Zeppelin documentation
|
||||
|
||||
This readme will walk you through building the Zeppelin documentation, which is included here with the Zeppelin source code.
|
||||
# Apache Zeppelin documentation
|
||||
|
||||
This README will walk you through building the documentation of Apache Zeppelin. The documentation is included here with Apache Zeppelin source code. The online documentation at [https://zeppelin.apache.org/docs/<ZEPPELIN_VERSION>](https://zeppelin.apache.org/docs/latest) is also generated from the files found in here.
|
||||
|
||||
## Build documentation
|
||||
See https://help.github.com/articles/using-jekyll-with-pages#installing-jekyll
|
||||
Zeppelin is using [Jekyll](https://jekyllrb.com/) which is a static site generator and [Github Pages](https://pages.github.com/) as a site publisher. For the more details, see [help.github.com/articles/about-github-pages-and-jekyll/](https://help.github.com/articles/about-github-pages-and-jekyll/).
|
||||
|
||||
**Requirements**
|
||||
|
||||
```
|
||||
ruby --version >= 2.0.0
|
||||
gem install bundler
|
||||
# go to /docs under your Zeppelin source
|
||||
bundle install
|
||||
# ruby --version >= 2.0.0
|
||||
# Install Bundler using gem
|
||||
gem install bundler
|
||||
|
||||
cd $ZEPPELIN_HOME/docs
|
||||
# Install all dependencies declared in the Gemfile
|
||||
bundle install
|
||||
```
|
||||
|
||||
For the further information about requirements, please see [here](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements).
|
||||
For the further information about requirements, please see [here](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements).
|
||||
|
||||
*On OS X 10.9 you may need to do "xcode-select --install"*
|
||||
On OS X 10.9, you may need to do
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
## Run website locally
|
||||
If you don't want to encounter uglily rendered pages, run the documentation site in your local first.
|
||||
|
||||
In `$ZEPPELIN_HOME/docs`,
|
||||
|
||||
```
|
||||
bundle exec jekyll serve --watch
|
||||
```
|
||||
|
||||
Using the above command, Jekyll will start a web server at `http://localhost:4000` and watch the `/docs` directory to update.
|
||||
|
||||
|
||||
## Run website
|
||||
|
||||
bundle exec jekyll serve --watch
|
||||
## Contribute to Zeppelin documentation
|
||||
If you wish to help us and contribute to Zeppelin Documentation, please look at [Zeppelin Documentation's contribution guideline](https://github.com/apache/zeppelin/blob/master/docs/CONTRIBUTING.md).
|
||||
|
||||
|
||||
## Adding a new page
|
||||
|
||||
rake page name="new-page.md"
|
||||
|
||||
|
||||
## Bumping up version in a new release
|
||||
## For committers only
|
||||
### Bumping up version in a new release
|
||||
|
||||
* `ZEPPELIN_VERSION` and `BASE_PATH` property in _config.yml
|
||||
|
||||
## Deploy to ASF svnpubsub infra (for committers only)
|
||||
### Deploy to ASF svnpubsub infra
|
||||
1. generate static website in `./_site`
|
||||
|
||||
```
|
||||
# go to /docs under Zeppelin source
|
||||
bundle exec jekyll build --safe
|
||||
|
|
|
|||
|
|
@ -198,7 +198,10 @@ The role of registered interpreters, settings and interpreters group are describ
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Fail code</td>
|
||||
<td> 500 </td>
|
||||
<td>
|
||||
400 if the input json is empty <br/>
|
||||
500 for any other errors
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sample JSON input</td>
|
||||
|
|
@ -219,7 +222,9 @@ The role of registered interpreters, settings and interpreters group are describ
|
|||
"dependencies": [
|
||||
{
|
||||
"groupArtifactVersion": "groupId:artifactId:version",
|
||||
"exclusions": "groupId:artifactId"
|
||||
"exclusions": [
|
||||
"groupId:artifactId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -249,7 +254,9 @@ The role of registered interpreters, settings and interpreters group are describ
|
|||
"dependencies": [
|
||||
{
|
||||
"groupArtifactVersion": "groupId:artifactId:version",
|
||||
"exclusions": "groupId:artifactId"
|
||||
"exclusions": [
|
||||
"groupId:artifactId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -298,7 +305,9 @@ The role of registered interpreters, settings and interpreters group are describ
|
|||
"dependencies": [
|
||||
{
|
||||
"groupArtifactVersion": "groupId:artifactId:version",
|
||||
"exclusions": "groupId:artifactId"
|
||||
"exclusions": [
|
||||
"groupId:artifactId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -328,7 +337,9 @@ The role of registered interpreters, settings and interpreters group are describ
|
|||
"dependencies": [
|
||||
{
|
||||
"groupArtifactVersion": "groupId:artifactId:version",
|
||||
"exclusions": "groupId:artifactId"
|
||||
"exclusions": [
|
||||
"groupId:artifactId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,6 +392,43 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
### Get the status of a single paragraph
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method gets the status of a single paragraph by the given notebook and paragraph id.
|
||||
The body field of the returned JSON contains of the array that compose of the paragraph id, paragraph status, paragraph finish date, paragraph started date.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/job/[notebookId]/[paragraphId]```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 500 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> sample JSON response </td>
|
||||
<td><pre>
|
||||
{
|
||||
"status": "OK",
|
||||
"body": {
|
||||
"id":"20151121-212654\_766735423",
|
||||
"status":"FINISHED",
|
||||
"finished":"Tue Nov 24 14:21:40 KST 2015",
|
||||
"started":"Tue Nov 24 14:21:39 KST 2015"
|
||||
}
|
||||
}</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
### Run a paragraph
|
||||
<table class="table-configuration">
|
||||
|
|
|
|||
|
|
@ -54,23 +54,15 @@ import java.util.Properties;
|
|||
* zeppelin.hbase.test.mode: (Testing only) Disable checks for unit and manual tests. Default: false
|
||||
*/
|
||||
public class HbaseInterpreter extends Interpreter {
|
||||
public static final String HBASE_HOME = "hbase.home";
|
||||
public static final String HBASE_RUBY_SRC = "hbase.ruby.sources";
|
||||
public static final String HBASE_TEST_MODE = "zeppelin.hbase.test.mode";
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(HbaseInterpreter.class);
|
||||
private ScriptingContainer scriptingContainer;
|
||||
|
||||
private StringWriter writer;
|
||||
|
||||
static {
|
||||
Interpreter.register("hbase", "hbase", HbaseInterpreter.class.getName(),
|
||||
new InterpreterPropertyBuilder()
|
||||
.add("hbase.home",
|
||||
getSystemDefault("HBASE_HOME", "hbase.home", "/usr/lib/hbase/"),
|
||||
"Installation directory of HBase")
|
||||
.add("hbase.ruby.sources", "lib/ruby",
|
||||
"Path to Ruby scripts relative to 'hbase.home'")
|
||||
.add("zeppelin.hbase.test.mode", "false", "Disable checks for unit and manual tests")
|
||||
.build());
|
||||
}
|
||||
|
||||
public HbaseInterpreter(Properties property) {
|
||||
super(property);
|
||||
}
|
||||
|
|
@ -81,9 +73,9 @@ public class HbaseInterpreter extends Interpreter {
|
|||
this.writer = new StringWriter();
|
||||
scriptingContainer.setOutput(this.writer);
|
||||
|
||||
if (!Boolean.parseBoolean(getProperty("zeppelin.hbase.test.mode"))) {
|
||||
String hbase_home = getProperty("hbase.home");
|
||||
String ruby_src = getProperty("hbase.ruby.sources");
|
||||
if (!Boolean.parseBoolean(getProperty(HBASE_TEST_MODE))) {
|
||||
String hbase_home = getProperty(HBASE_HOME);
|
||||
String ruby_src = getProperty(HBASE_RUBY_SRC);
|
||||
Path abs_ruby_src = Paths.get(hbase_home, ruby_src).toAbsolutePath();
|
||||
|
||||
logger.info("Home:" + hbase_home);
|
||||
|
|
@ -98,7 +90,7 @@ public class HbaseInterpreter extends Interpreter {
|
|||
logger.info("Absolute Ruby Source:" + abs_ruby_src.toString());
|
||||
// hirb.rb:41 requires the following system property to be set.
|
||||
Properties sysProps = System.getProperties();
|
||||
sysProps.setProperty("hbase.ruby.sources", abs_ruby_src.toString());
|
||||
sysProps.setProperty(HBASE_RUBY_SRC, abs_ruby_src.toString());
|
||||
|
||||
Path abs_hirb_path = Paths.get(hbase_home, "bin/hirb.rb");
|
||||
try {
|
||||
|
|
|
|||
25
hbase/src/main/resources/interpreter-setting.json
Normal file
25
hbase/src/main/resources/interpreter-setting.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[
|
||||
{
|
||||
"group": "hbase",
|
||||
"name": "hbase",
|
||||
"className": "org.apache.zeppelin.hbase.HbaseInterpreter",
|
||||
"properties": {
|
||||
"hbase.home": {
|
||||
"envName": "HBASE_HOME",
|
||||
"propertyName": "hbase.home",
|
||||
"defaultValue": "/usr/lib/hbase/",
|
||||
"description": "Installation directory of HBase"
|
||||
},
|
||||
"hbase.ruby.sources": {
|
||||
"propertyName": "hbase.ruby.sources",
|
||||
"defaultValue": "lib/ruby",
|
||||
"description": "Path to Ruby scripts relative to 'hbase.home'"
|
||||
},
|
||||
"zeppelin.hbase.test.mode": {
|
||||
"propertyName": "zeppelin.hbase.test.mode",
|
||||
"defaultValue": "false",
|
||||
"description": "Disable checks for unit and manual tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
1
pom.xml
1
pom.xml
|
|
@ -486,6 +486,7 @@
|
|||
<exclude>docs/sitemap.txt</exclude>
|
||||
<exclude>docs/search_data.json</exclude>
|
||||
<exclude>**/dependency-reduced-pom.xml</exclude>
|
||||
<exclude>docs/CONTRIBUTING.md</exclude>
|
||||
|
||||
<!-- bundled from anchor -->
|
||||
<exclude>docs/assets/themes/zeppelin/js/anchor.min.js</exclude>
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@
|
|||
import sys
|
||||
import signal
|
||||
import base64
|
||||
|
||||
from io import BytesIO
|
||||
try:
|
||||
import StringIO as io
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
import io as io
|
||||
from io import StringIO
|
||||
|
||||
def intHandler(signum, frame): # Set the signal handler
|
||||
print ("Paragraph interrupted")
|
||||
|
|
@ -117,6 +117,7 @@ class PyZeppelinContext(object):
|
|||
|
||||
def __init__(self):
|
||||
self.max_result = 1000
|
||||
self.py3 = bool(sys.version_info >= (3,))
|
||||
|
||||
def input(self, name, defaultValue=""):
|
||||
print(self.errorMsg)
|
||||
|
|
@ -141,14 +142,14 @@ class PyZeppelinContext(object):
|
|||
"""Pretty prints DF using Table Display System
|
||||
"""
|
||||
limit = len(df) > self.max_result
|
||||
header_buf = io.StringIO("")
|
||||
header_buf = StringIO("")
|
||||
header_buf.write(str(df.columns[0]))
|
||||
for col in df.columns[1:]:
|
||||
header_buf.write("\t")
|
||||
header_buf.write(str(col))
|
||||
header_buf.write("\n")
|
||||
|
||||
body_buf = io.StringIO("")
|
||||
body_buf = StringIO("")
|
||||
rows = df.head(self.max_result).values if limit else df.values
|
||||
for row in rows:
|
||||
body_buf.write(str(row[0]))
|
||||
|
|
@ -168,13 +169,18 @@ class PyZeppelinContext(object):
|
|||
fmt='png', **kwargs):
|
||||
"""Matplotlib show function
|
||||
"""
|
||||
img = io.StringIO()
|
||||
if fmt == 'png':
|
||||
img = BytesIO()
|
||||
p.savefig(img, format=fmt)
|
||||
html = "%html <img src={img} width={width}, height={height}>"
|
||||
img_str = "data:image/png;base64,"
|
||||
img_str = b"data:image/png;base64,"
|
||||
img_str += base64.b64encode(img.getvalue().strip())
|
||||
# Need to do this for python3 compatibility
|
||||
if self.py3:
|
||||
img_str = img_str.decode('ascii')
|
||||
|
||||
elif fmt == 'svg':
|
||||
img = StringIO()
|
||||
p.savefig(img, format=fmt)
|
||||
html = "%html <div style='width:{width};height:{height}'>{img}<div>"
|
||||
img_str = img.getvalue()
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
private ByteArrayOutputStream input;
|
||||
private String scriptPath;
|
||||
boolean pythonscriptRunning = false;
|
||||
private static final int MAX_TIMEOUT_SEC = 10;
|
||||
|
||||
public PySparkInterpreter(Properties property) {
|
||||
super(property);
|
||||
|
|
@ -316,7 +317,7 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
long startTime = System.currentTimeMillis();
|
||||
while (pythonScriptInitialized == false
|
||||
&& pythonscriptRunning
|
||||
&& System.currentTimeMillis() - startTime < 10 * 1000) {
|
||||
&& System.currentTimeMillis() - startTime < MAX_TIMEOUT_SEC * 1000) {
|
||||
try {
|
||||
pythonScriptInitializeNotifier.wait(1000);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
@ -423,8 +424,15 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
|
|||
}
|
||||
|
||||
synchronized (statementFinishedNotifier) {
|
||||
while (statementOutput == null) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (statementOutput == null
|
||||
&& pythonScriptInitialized == false
|
||||
&& pythonscriptRunning) {
|
||||
try {
|
||||
if (System.currentTimeMillis() - startTime < MAX_TIMEOUT_SEC * 1000) {
|
||||
logger.error("pyspark completion didn't have response for {}sec.", MAX_TIMEOUT_SEC);
|
||||
break;
|
||||
}
|
||||
statementFinishedNotifier.wait(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// not working
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ class Utils {
|
|||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ The following components are provided under Apache License.
|
|||
(Apache 2.0) Utility classes for Jetty (org.mortbay.jetty:jetty-util:6.1.26 - http://javadox.com/org.mortbay.jetty/jetty/6.1.26/overview-tree.html)
|
||||
(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)
|
||||
|
||||
========================================================================
|
||||
MIT licenses
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ public class InterpreterRestApi {
|
|||
try {
|
||||
NewInterpreterSettingRequest request =
|
||||
gson.fromJson(message, NewInterpreterSettingRequest.class);
|
||||
if (request == null) {
|
||||
return new JsonResponse<>(Status.BAD_REQUEST).build();
|
||||
}
|
||||
Properties p = new Properties();
|
||||
p.putAll(request.getProperties());
|
||||
InterpreterSetting interpreterSetting = interpreterFactory
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ public class NotebookRestApi {
|
|||
* TODO(jl): Fixed the type of HashSet
|
||||
* https://issues.apache.org/jira/browse/ZEPPELIN-1162
|
||||
*/
|
||||
HashMap<String, HashSet> permMap =
|
||||
gson.fromJson(req, new TypeToken<HashMap<String, HashSet>>() {
|
||||
HashMap<String, HashSet<String>> permMap =
|
||||
gson.fromJson(req, new TypeToken<HashMap<String, HashSet<String>>>() {
|
||||
}.getType());
|
||||
Note note = notebook.getNote(noteId);
|
||||
String principal = SecurityUtils.getPrincipal();
|
||||
|
|
@ -133,9 +133,9 @@ public class NotebookRestApi {
|
|||
ownerPermissionError(userAndRoles, notebookAuthorization.getOwners(noteId))).build();
|
||||
}
|
||||
|
||||
HashSet readers = permMap.get("readers");
|
||||
HashSet owners = permMap.get("owners");
|
||||
HashSet writers = permMap.get("writers");
|
||||
HashSet<String> readers = permMap.get("readers");
|
||||
HashSet<String> owners = permMap.get("owners");
|
||||
HashSet<String> writers = permMap.get("writers");
|
||||
// Set readers, if writers and owners is empty -> set to user requesting the change
|
||||
if (readers != null && !readers.isEmpty()) {
|
||||
if (writers.isEmpty()) {
|
||||
|
|
@ -528,6 +528,35 @@ public class NotebookRestApi {
|
|||
return new JsonResponse<>(Status.OK, null, note.generateParagraphsInfo()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notebook paragraph job status REST API
|
||||
*
|
||||
* @param notebookId ID of Notebook
|
||||
* @param paragraphId ID of Paragraph
|
||||
* @return JSON with status.OK
|
||||
* @throws IOException, IllegalArgumentException
|
||||
*/
|
||||
@GET
|
||||
@Path("job/{notebookId}/{paragraphId}")
|
||||
@ZeppelinApi
|
||||
public Response getNoteParagraphJobStatus(@PathParam("notebookId") String notebookId,
|
||||
@PathParam("paragraphId") String paragraphId)
|
||||
throws IOException, IllegalArgumentException {
|
||||
LOG.info("get notebook paragraph job status.");
|
||||
Note note = notebook.getNote(notebookId);
|
||||
if (note == null) {
|
||||
return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build();
|
||||
}
|
||||
|
||||
Paragraph paragraph = note.getParagraph(paragraphId);
|
||||
if (paragraph == null) {
|
||||
return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build();
|
||||
}
|
||||
|
||||
return new JsonResponse<>(Status.OK, null, note.generateSingleParagraphInfo(paragraphId)).
|
||||
build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run paragraph job REST API
|
||||
*
|
||||
|
|
|
|||
|
|
@ -119,6 +119,15 @@ public class InterpreterRestApiTest extends AbstractTestRestApi {
|
|||
delete.releaseConnection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsCreateWithEmptyJson() throws IOException {
|
||||
// Call Create Setting REST API
|
||||
PostMethod post = httpPost("/interpreter/setting/", "");
|
||||
LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString());
|
||||
assertThat("test create method:", post, isBadRequest());
|
||||
post.releaseConnection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterpreterAutoBinding() throws IOException {
|
||||
// create note
|
||||
|
|
|
|||
|
|
@ -122,6 +122,28 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
|
|||
ZeppelinServer.notebook.removeNote(note2.getId(), null);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNoteParagraphJobStatus() throws IOException {
|
||||
Note note1 = ZeppelinServer.notebook.createNote(null);
|
||||
note1.addParagraph();
|
||||
|
||||
String paragraphId = note1.getLastParagraph().getId();
|
||||
|
||||
GetMethod get = httpGet("/notebook/job/" + note1.getId() + "/" + paragraphId);
|
||||
assertThat(get, isAllowed());
|
||||
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
|
||||
}.getType());
|
||||
Map<String, Set<String>> paragraphStatus = (Map<String, Set<String>>) resp.get("body");
|
||||
|
||||
// Check id and status have proper value
|
||||
assertEquals(paragraphStatus.get("id"), paragraphId);
|
||||
assertEquals(paragraphStatus.get("status"), "READY");
|
||||
|
||||
//cleanup
|
||||
ZeppelinServer.notebook.removeNote(note1.getId(), null);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
|
||||
"handsontable": "~0.24.2",
|
||||
"moment-duration-format": "^1.3.0",
|
||||
"select2": "^4.0.3"
|
||||
"select2": "^4.0.3",
|
||||
"angular-esri-map": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.5.0"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@
|
|||
'xeditable',
|
||||
'ngToast',
|
||||
'focus-if',
|
||||
'ngResource'
|
||||
'ngResource',
|
||||
'esri.map'
|
||||
])
|
||||
.filter('breakFilter', function() {
|
||||
return function(text) {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
|
|||
var initNotebook = function() {
|
||||
websocketMsgSrv.getNotebook($routeParams.noteId);
|
||||
websocketMsgSrv.listRevisionHistory($routeParams.noteId);
|
||||
|
||||
var currentRoute = $route.current;
|
||||
if (currentRoute) {
|
||||
setTimeout(
|
||||
|
|
@ -333,6 +332,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
|
|||
} else {
|
||||
$scope.viewOnly = $scope.note.config.looknfeel === 'report' ? true : false;
|
||||
}
|
||||
$scope.note.paragraphs[0].focus = true;
|
||||
$rootScope.$broadcast('setLookAndFeel', $scope.note.config.looknfeel);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ limitations under the License.
|
|||
ng-class="{'active': isGraphMode('scatterChart')}"
|
||||
ng-click="setGraphMode('scatterChart', true)"><i class="cf cf-scatter-chart"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-if="paragraph.result.type == 'TABLE'"
|
||||
ng-class="{'active': isGraphMode('map')}"
|
||||
ng-click="setGraphMode('map', true)"><i class="fa fa-map-marker"></i>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
ng-if="paragraph.result.type != 'TABLE'"
|
||||
|
|
|
|||
|
|
@ -51,4 +51,12 @@ limitations under the License.
|
|||
id="p{{paragraph.id}}_scatterChart">
|
||||
<svg></svg>
|
||||
</div>
|
||||
|
||||
<div ng-if="getGraphMode()=='map'" id="p{{paragraph.id}}_map"
|
||||
ng-switch="paragraph.config.graph.map.isOnline">
|
||||
<div ng-switch-when="true"></div>
|
||||
<span class="map-offline-text" ng-switch-default>
|
||||
<span>Maps require internet connectivity.</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,4 +28,19 @@ limitations under the License.
|
|||
show line chart with focus
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="isGraphMode('map')">
|
||||
<label>Basemap</label>
|
||||
<span class="dropdown">
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" style="min-width: 0px;" data-toggle="dropdown">
|
||||
<span ng-bind="paragraph.config.graph.map.baseMapType"></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" style="min-width: 70px;">
|
||||
<li ng-repeat="opt in baseMapOption">
|
||||
<a ng-click="setMapBaseMap(opt)">{{opt}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ limitations under the License.
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="getGraphMode()!='scatterChart'">
|
||||
<div class="row" ng-if="getGraphMode()!='scatterChart' && getGraphMode()!='map'">
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Keys
|
||||
|
|
@ -165,4 +165,52 @@ limitations under the License.
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="getGraphMode()=='map'">
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Latitude
|
||||
<ul data-drop="true"
|
||||
ng-model="paragraph.config.graph.map.lat"
|
||||
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-if="paragraph.config.graph.map.lat">
|
||||
<div class="btn btn-primary btn-xs">
|
||||
{{paragraph.config.graph.map.lat.name}} <span class="fa fa-close" ng-click="removeMapOptionLat($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Longitude
|
||||
<ul data-drop="true"
|
||||
ng-model="paragraph.config.graph.map.lng"
|
||||
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-if="paragraph.config.graph.map.lng">
|
||||
<div class="btn btn-primary btn-xs">
|
||||
{{paragraph.config.graph.map.lng.name}} <span class="fa fa-close" ng-click="removeMapOptionLng($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="columns lightBold">
|
||||
Pin contents
|
||||
<ul data-drop="true"
|
||||
ng-model="paragraph.config.graph.map.pinCols"
|
||||
jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
|
||||
class="list-unstyled">
|
||||
<li ng-repeat="col in paragraph.config.graph.map.pinCols">
|
||||
<div class="btn btn-primary btn-xs">
|
||||
{{col.name}} <span class="fa fa-close" ng-click="removeMapOptionPinInfo($index)"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window,
|
||||
$routeParams, $location, $timeout, $compile,
|
||||
$http, websocketMsgSrv, baseUrlSrv, ngToast,
|
||||
saveAsService) {
|
||||
saveAsService, esriLoader) {
|
||||
var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
|
||||
$scope.parentNote = null;
|
||||
$scope.paragraph = null;
|
||||
|
|
@ -93,12 +93,12 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$scope.parentNote = note;
|
||||
$scope.originalText = angular.copy(newParagraph.text);
|
||||
$scope.chart = {};
|
||||
$scope.baseMapOption = ['Streets', 'Satellite', 'Hybrid', 'Topo', 'Gray', 'Oceans', 'Terrain'];
|
||||
$scope.colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
$scope.paragraphFocused = false;
|
||||
if (newParagraph.focus) {
|
||||
$scope.paragraphFocused = true;
|
||||
}
|
||||
|
||||
if (!$scope.paragraph.config) {
|
||||
$scope.paragraph.config = {};
|
||||
}
|
||||
|
|
@ -245,6 +245,22 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
config.graph.scatter = {};
|
||||
}
|
||||
|
||||
if (!config.graph.map) {
|
||||
config.graph.map = {};
|
||||
}
|
||||
|
||||
if (!config.graph.map.baseMapType) {
|
||||
config.graph.map.baseMapType = $scope.baseMapOption[0];
|
||||
}
|
||||
|
||||
if (!config.graph.map.isOnline) {
|
||||
config.graph.map.isOnline = true;
|
||||
}
|
||||
|
||||
if (!config.graph.map.pinCols) {
|
||||
config.graph.map.pinCols = [];
|
||||
}
|
||||
|
||||
if (config.enabled === undefined) {
|
||||
config.enabled = true;
|
||||
}
|
||||
|
|
@ -539,9 +555,10 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$scope.editor.setHighlightGutterLine(false);
|
||||
$scope.editor.getSession().setUseWrapMode(true);
|
||||
$scope.editor.setTheme('ace/theme/chrome');
|
||||
$scope.editor.setReadOnly($scope.isRunning());
|
||||
if ($scope.paragraphFocused) {
|
||||
$scope.editor.focus();
|
||||
$scope.goToLineEnd();
|
||||
$scope.goToEnd();
|
||||
}
|
||||
|
||||
autoAdjustEditorHeight(_editor.container.id);
|
||||
|
|
@ -822,8 +839,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
return false;
|
||||
};
|
||||
|
||||
$scope.goToLineEnd = function() {
|
||||
$scope.editor.navigateLineEnd();
|
||||
$scope.goToEnd = function() {
|
||||
$scope.editor.navigateFileEnd();
|
||||
};
|
||||
|
||||
$scope.getResultType = function(paragraph) {
|
||||
|
|
@ -907,6 +924,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
|
||||
if (!type || type === 'table') {
|
||||
setTable($scope.paragraph.result, refresh);
|
||||
} else if (type === 'map') {
|
||||
setMap($scope.paragraph.result, refresh);
|
||||
} else {
|
||||
setD3Chart(type, $scope.paragraph.result, refresh);
|
||||
}
|
||||
|
|
@ -1034,7 +1053,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
d3g = scatterData.d3g;
|
||||
|
||||
$scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);});
|
||||
$scope.chart[type].yAxis.tickFormat(function(d) {return xAxisTickFormat(d, yLabels);});
|
||||
$scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, yLabels);});
|
||||
|
||||
// configure how the tooltip looks.
|
||||
$scope.chart[type].tooltipContent(function(key, x, y, graph, data) {
|
||||
|
|
@ -1076,7 +1095,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
xLabels = pivotdata.xLabels;
|
||||
d3g = pivotdata.d3g;
|
||||
$scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);});
|
||||
$scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d);});
|
||||
if (type === 'stackedAreaChart') {
|
||||
$scope.chart[type].yAxisTickFormat(function(d) {return yAxisTickFormat(d);});
|
||||
} else {
|
||||
$scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, xLabels);});
|
||||
}
|
||||
$scope.chart[type].yAxis.axisLabelDistance(50);
|
||||
if ($scope.chart[type].useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline
|
||||
$scope.chart[type].useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
|
||||
|
|
@ -1131,6 +1154,236 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$timeout(retryRenderer);
|
||||
};
|
||||
|
||||
var setMap = function(data, refresh) {
|
||||
var createPinMapLayer = function(pins, cb) {
|
||||
esriLoader.require(['esri/layers/FeatureLayer'], function(FeatureLayer) {
|
||||
var pinLayer = new FeatureLayer({
|
||||
id: 'pins',
|
||||
spatialReference: $scope.map.spatialReference,
|
||||
geometryType: 'point',
|
||||
source: pins,
|
||||
fields: [],
|
||||
objectIdField: '_ObjectID',
|
||||
renderer: $scope.map.pinRenderer,
|
||||
popupTemplate: {
|
||||
title: '[{_lng}, {_lat}]',
|
||||
content: [{
|
||||
type: 'fields',
|
||||
fieldInfos: []
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
// add user-selected pin info fields to popup
|
||||
var pinInfoCols = $scope.paragraph.config.graph.map.pinCols;
|
||||
for (var i = 0; i < pinInfoCols.length; ++i) {
|
||||
pinLayer.popupTemplate.content[0].fieldInfos.push({
|
||||
fieldName: pinInfoCols[i].name,
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
cb(pinLayer);
|
||||
});
|
||||
};
|
||||
|
||||
var getMapPins = function(cb) {
|
||||
esriLoader.require(['esri/geometry/Point'], function(Point, FeatureLayer) {
|
||||
var latCol = $scope.paragraph.config.graph.map.lat;
|
||||
var lngCol = $scope.paragraph.config.graph.map.lng;
|
||||
var pinInfoCols = $scope.paragraph.config.graph.map.pinCols;
|
||||
var pins = [];
|
||||
|
||||
// construct objects for pins
|
||||
if (latCol && lngCol && data.rows) {
|
||||
for (var i = 0; i < data.rows.length; ++i) {
|
||||
var row = data.rows[i];
|
||||
var lng = row[lngCol.index];
|
||||
var lat = row[latCol.index];
|
||||
var pin = {
|
||||
geometry: new Point({
|
||||
longitude: lng,
|
||||
latitude: lat,
|
||||
spatialReference: $scope.map.spatialReference
|
||||
}),
|
||||
attributes: {
|
||||
_ObjectID: i,
|
||||
_lng: lng,
|
||||
_lat: lat
|
||||
}
|
||||
};
|
||||
|
||||
// add pin info from user-selected columns
|
||||
for (var j = 0; j < pinInfoCols.length; ++j) {
|
||||
var col = pinInfoCols[j];
|
||||
pin.attributes[col.name] = row[col.index];
|
||||
}
|
||||
pins.push(pin);
|
||||
}
|
||||
}
|
||||
cb(pins);
|
||||
});
|
||||
};
|
||||
|
||||
var updateMapPins = function() {
|
||||
var pinLayer = $scope.map.map.findLayerById('pins');
|
||||
$scope.map.popup.close();
|
||||
if (pinLayer) {
|
||||
$scope.map.map.remove(pinLayer);
|
||||
}
|
||||
|
||||
// add pins to map as layer
|
||||
getMapPins(function(pins) {
|
||||
createPinMapLayer(pins, function(pinLayer) {
|
||||
$scope.map.map.add(pinLayer);
|
||||
if (pinLayer.source.length > 0) {
|
||||
$scope.map.goTo(pinLayer.source);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var createMap = function(mapdiv) {
|
||||
// prevent zooming with the scroll wheel
|
||||
var disableZoom = function(e) {
|
||||
var evt = e || window.event;
|
||||
evt.cancelBubble = true;
|
||||
evt.returnValue = false;
|
||||
if (evt.stopPropagation) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
};
|
||||
var eName = window.WheelEvent ? 'wheel' : // Modern browsers
|
||||
window.MouseWheelEvent ? 'mousewheel' : // WebKit and IE
|
||||
'DOMMouseScroll'; // Old Firefox
|
||||
mapdiv.addEventListener(eName, disableZoom, true);
|
||||
|
||||
esriLoader.require(['esri/views/MapView',
|
||||
'esri/Map',
|
||||
'esri/renderers/SimpleRenderer',
|
||||
'esri/symbols/SimpleMarkerSymbol'],
|
||||
function(MapView, Map, SimpleRenderer, SimpleMarkerSymbol) {
|
||||
$scope.map = new MapView({
|
||||
container: mapdiv,
|
||||
map: new Map({
|
||||
basemap: $scope.paragraph.config.graph.map.baseMapType.toLowerCase()
|
||||
}),
|
||||
center: [-106.3468, 56.1304], // Canada (lng, lat)
|
||||
zoom: 2,
|
||||
pinRenderer: new SimpleRenderer({
|
||||
symbol: new SimpleMarkerSymbol({
|
||||
'color': [255, 0, 0, 0.5],
|
||||
'size': 16.5,
|
||||
'outline': {
|
||||
'color': [0, 0, 0, 1],
|
||||
'width': 1.125,
|
||||
},
|
||||
// map pin SVG path
|
||||
'path': 'M16,3.5c-4.142,0-7.5,3.358-7.5,7.5c0,4.143,7.5,18.121,7.5,' +
|
||||
'18.121S23.5,15.143,23.5,11C23.5,6.858,20.143,3.5,16,3.5z ' +
|
||||
'M16,14.584c-1.979,0-3.584-1.604-3.584-3.584S14.021,7.416,' +
|
||||
'16,7.416S19.584,9.021,19.584,11S17.979,14.584,16,14.584z'
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
$scope.map.on('click', function() {
|
||||
// ArcGIS JS API 4.0 does not account for scrolling or position
|
||||
// changes by default (this is a bug, to be fixed in the upcoming
|
||||
// version 4.1; see https://geonet.esri.com/thread/177238#comment-609681).
|
||||
// This results in a misaligned popup.
|
||||
|
||||
// Workaround: manually set popup position to match position of selected pin
|
||||
if ($scope.map.popup.selectedFeature) {
|
||||
$scope.map.popup.location = $scope.map.popup.selectedFeature.geometry;
|
||||
}
|
||||
});
|
||||
$scope.map.then(updateMapPins);
|
||||
});
|
||||
};
|
||||
|
||||
var checkMapOnline = function(cb) {
|
||||
// are we able to get a response from the ArcGIS servers?
|
||||
var callback = function(res) {
|
||||
var online = (res.status > 0);
|
||||
$scope.paragraph.config.graph.map.isOnline = online;
|
||||
cb(online);
|
||||
};
|
||||
$http.head('//services.arcgisonline.com/arcgis/', {
|
||||
timeout: 5000,
|
||||
withCredentials: false
|
||||
}).then(callback, callback);
|
||||
};
|
||||
|
||||
var renderMap = function() {
|
||||
var mapdiv = angular.element('#p' + $scope.paragraph.id + '_map')
|
||||
.css('height', $scope.paragraph.config.graph.height)
|
||||
.children('div').get(0);
|
||||
|
||||
// on chart type change, destroy map to force reinitialization.
|
||||
if ($scope.map && !refresh) {
|
||||
$scope.map.map.destroy();
|
||||
$scope.map.pinRenderer = null;
|
||||
$scope.map = null;
|
||||
}
|
||||
|
||||
var requireMapCSS = function() {
|
||||
var url = '//js.arcgis.com/4.0/esri/css/main.css';
|
||||
if (!angular.element('link[href="' + url + '"]').length) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = url;
|
||||
angular.element('head').append(link);
|
||||
}
|
||||
};
|
||||
|
||||
var requireMapJS = function(cb) {
|
||||
if (!esriLoader.isLoaded()) {
|
||||
esriLoader.bootstrap({
|
||||
url: '//js.arcgis.com/4.0'
|
||||
}).then(cb);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
checkMapOnline(function(online) {
|
||||
// we need an internet connection to use the map
|
||||
if (online) {
|
||||
// create map if not exists.
|
||||
if (!$scope.map) {
|
||||
requireMapCSS();
|
||||
requireMapJS(function() {
|
||||
createMap(mapdiv);
|
||||
});
|
||||
} else {
|
||||
updateMapPins();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var retryRenderer = function() {
|
||||
if (angular.element('#p' + $scope.paragraph.id + '_map div').length) {
|
||||
try {
|
||||
renderMap();
|
||||
} catch (err) {
|
||||
console.log('Map drawing error %o', err);
|
||||
}
|
||||
} else {
|
||||
$timeout(retryRenderer,10);
|
||||
}
|
||||
};
|
||||
$timeout(retryRenderer);
|
||||
};
|
||||
|
||||
$scope.setMapBaseMap = function(bm) {
|
||||
$scope.paragraph.config.graph.map.baseMapType = bm;
|
||||
if ($scope.map) {
|
||||
$scope.map.map.basemap = bm.toLowerCase();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isGraphMode = function(graphName) {
|
||||
var activeAppId = _.get($scope.paragraph.config, 'helium.activeApp');
|
||||
if ($scope.getResultType() === 'TABLE' && $scope.getGraphMode() === graphName && !activeAppId) {
|
||||
|
|
@ -1193,6 +1446,24 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
|
||||
};
|
||||
|
||||
$scope.removeMapOptionLat = function(idx) {
|
||||
$scope.paragraph.config.graph.map.lat = null;
|
||||
clearUnknownColsFromGraphOption();
|
||||
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
|
||||
};
|
||||
|
||||
$scope.removeMapOptionLng = function(idx) {
|
||||
$scope.paragraph.config.graph.map.lng = null;
|
||||
clearUnknownColsFromGraphOption();
|
||||
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
|
||||
};
|
||||
|
||||
$scope.removeMapOptionPinInfo = function(idx) {
|
||||
$scope.paragraph.config.graph.map.pinCols.splice(idx, 1);
|
||||
clearUnknownColsFromGraphOption();
|
||||
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
|
||||
};
|
||||
|
||||
/* Clear unknown columns from graph option */
|
||||
var clearUnknownColsFromGraphOption = function() {
|
||||
var unique = function(list) {
|
||||
|
|
@ -1223,7 +1494,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
}
|
||||
};
|
||||
|
||||
var removeUnknownFromScatterSetting = function(fields) {
|
||||
var removeUnknownFromFields = function(fields) {
|
||||
for (var f in fields) {
|
||||
if (fields[f]) {
|
||||
var found = false;
|
||||
|
|
@ -1235,7 +1506,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (!found && (fields[f] instanceof Object) && !(fields[f] instanceof Array)) {
|
||||
fields[f] = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1250,7 +1521,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
unique($scope.paragraph.config.graph.groups);
|
||||
removeUnknown($scope.paragraph.config.graph.groups);
|
||||
|
||||
removeUnknownFromScatterSetting($scope.paragraph.config.graph.scatter);
|
||||
removeUnknownFromFields($scope.paragraph.config.graph.scatter);
|
||||
|
||||
unique($scope.paragraph.config.graph.map.pinCols);
|
||||
removeUnknown($scope.paragraph.config.graph.map.pinCols);
|
||||
removeUnknownFromFields($scope.paragraph.config.graph.map);
|
||||
};
|
||||
|
||||
/* select default key and value if there're none selected */
|
||||
|
|
@ -1271,6 +1546,23 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$scope.paragraph.config.graph.scatter.xAxis = $scope.paragraph.result.columnNames[0];
|
||||
}
|
||||
}
|
||||
|
||||
/* try to find columns for the map logitude and latitude */
|
||||
var findDefaultMapCol = function(settingName, keyword) {
|
||||
var col;
|
||||
if (!$scope.paragraph.config.graph.map[settingName]) {
|
||||
for (var i = 0; i < $scope.paragraph.result.columnNames.length; ++i) {
|
||||
col = $scope.paragraph.result.columnNames[i];
|
||||
if (col.name.toUpperCase().indexOf(keyword) !== -1) {
|
||||
$scope.paragraph.config.graph.map[settingName] = col;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
findDefaultMapCol('lat', 'LAT');
|
||||
findDefaultMapCol('lng', 'LONG');
|
||||
};
|
||||
|
||||
var pivot = function(data) {
|
||||
|
|
@ -2228,6 +2520,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
|
|||
$scope.paragraph.status = data.paragraph.status;
|
||||
$scope.paragraph.result = data.paragraph.result;
|
||||
$scope.paragraph.settings = data.paragraph.settings;
|
||||
$scope.editor.setReadOnly($scope.isRunning());
|
||||
|
||||
if (!$scope.asIframe) {
|
||||
$scope.paragraph.config = data.paragraph.config;
|
||||
|
|
|
|||
|
|
@ -275,6 +275,10 @@ table.dataTable.table-condensed .sorting_desc:after {
|
|||
background: none !important;
|
||||
}
|
||||
|
||||
.paragraph-disable {
|
||||
opacity : 0.6!important;
|
||||
}
|
||||
|
||||
.ace_marker-layer .ace_selection {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
|
@ -327,12 +331,41 @@ table.dataTable.table-condensed .sorting_desc:after {
|
|||
.tableDisplay div {
|
||||
}
|
||||
|
||||
.tableDisplay img {
|
||||
.tableDisplay img:not(.esri-bitmap) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.esri-display-object > svg {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.esri-popup > .esri-docked.esri-dock-to-bottom {
|
||||
padding: 8px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.esri-popup-main {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
span.map-offline-text {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.map-offline-text > span {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.tableDisplay .btn-group span {
|
||||
margin: 10px 0 0 10px;
|
||||
font-size: 12px;
|
||||
|
|
@ -350,7 +383,8 @@ table.dataTable.table-condensed .sorting_desc:after {
|
|||
|
||||
}
|
||||
|
||||
.tableDisplay .option .columns {
|
||||
.tableDisplay .option .columns,
|
||||
div.esri-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,16 +38,14 @@ limitations under the License.
|
|||
<div>
|
||||
<div ng-show="!paragraph.config.editorHide && !viewOnly" style="margin-bottom:3px;">
|
||||
<div id="{{paragraph.id}}_editor"
|
||||
style="opacity: 1;"
|
||||
class="editor"
|
||||
ui-ace="{
|
||||
onLoad : aceLoaded,
|
||||
require : ['ace/ext/language_tools']
|
||||
}"
|
||||
ng-model="paragraph.text"
|
||||
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING',
|
||||
'paragraph-text--dirty' : dirtyText !== originalText && dirtyText !== undefined}">
|
||||
</div>
|
||||
ng-class="{'paragraph-disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING',
|
||||
'paragraph-text--dirty' : dirtyText !== originalText && dirtyText !== undefined}"> </div>
|
||||
</div>
|
||||
|
||||
<div ng-include src="'app/notebook/paragraph/paragraph-progressBar.html'"></div>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ angular.module('zeppelinWebApp').directive('ngEnter', function() {
|
|||
return function(scope, element, attrs) {
|
||||
element.bind('keydown keypress', function(event) {
|
||||
if (event.which === 13) {
|
||||
scope.$apply(function() {
|
||||
scope.$eval(attrs.ngEnter);
|
||||
});
|
||||
if (!event.shiftKey) {
|
||||
scope.$apply(function() {
|
||||
scope.$eval(attrs.ngEnter);
|
||||
});
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ limitations under the License.
|
|||
<script src="bower_components/handsontable/dist/handsontable.js"></script>
|
||||
<script src="bower_components/moment-duration-format/lib/moment-duration-format.js"></script>
|
||||
<script src="bower_components/select2/dist/js/select2.js"></script>
|
||||
<script src="bower_components/angular-esri-map/dist/angular-esri-map.js"></script>
|
||||
<!-- endbower -->
|
||||
<!-- endbuild -->
|
||||
<!-- build:js({.tmp,src}) scripts/scripts.js -->
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ module.exports = function(config) {
|
|||
'bower_components/handsontable/dist/handsontable.js',
|
||||
'bower_components/moment-duration-format/lib/moment-duration-format.js',
|
||||
'bower_components/select2/dist/js/select2.js',
|
||||
'bower_components/angular-esri-map/dist/angular-esri-map.js',
|
||||
'bower_components/angular-mocks/angular-mocks.js',
|
||||
// endbower
|
||||
'src/app/app.js',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe('Controller: ParagraphCtrl', function() {
|
|||
'getResultType', 'loadTableData', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange',
|
||||
'removeGraphOptionKeys', 'removeGraphOptionValues', 'removeGraphOptionGroups', 'setGraphOptionValueAggr',
|
||||
'removeScatterOptionXaxis', 'removeScatterOptionYaxis', 'removeScatterOptionGroup',
|
||||
'removeScatterOptionSize'];
|
||||
'removeScatterOptionSize', 'removeMapOptionLat', 'removeMapOptionLng', 'removeMapOptionPinInfo'];
|
||||
|
||||
functions.forEach(function(fn) {
|
||||
it('check for scope functions to be defined : ' + fn, function() {
|
||||
|
|
|
|||
|
|
@ -97,13 +97,13 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
* This is only references with default settings, name and properties
|
||||
* key: InterpreterSetting.name
|
||||
*/
|
||||
private Map<String, InterpreterSetting> interpreterSettingsRef = new HashMap<>();
|
||||
private final Map<String, InterpreterSetting> interpreterSettingsRef = new HashMap<>();
|
||||
|
||||
/**
|
||||
* This is used by creating and running Interpreters
|
||||
* key: InterpreterSetting.id <- This is becuase backward compatibility
|
||||
*/
|
||||
private Map<String, InterpreterSetting> interpreterSettings = new HashMap<>();
|
||||
private final Map<String, InterpreterSetting> interpreterSettings = new HashMap<>();
|
||||
|
||||
private Map<String, List<String>> interpreterBindings = new HashMap<>();
|
||||
private List<RemoteRepository> interpreterRepositories;
|
||||
|
|
@ -175,7 +175,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
|
||||
registerInterpreterFromResource(cl, interpreterDirString, interpreterJson);
|
||||
|
||||
/**
|
||||
/*
|
||||
* TODO(jongyoul)
|
||||
* - Remove these codes below because of legacy code
|
||||
* - Support ThreadInterpreter
|
||||
|
|
@ -468,8 +468,6 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
* Return ordered interpreter setting list.
|
||||
* The list does not contain more than one setting from the same interpreter class.
|
||||
* Order by InterpreterClass (order defined by ZEPPELIN_INTERPRETERS), Interpreter setting name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<String> getDefaultInterpreterSettingList() {
|
||||
// this list will contain default interpreter setting list
|
||||
|
|
@ -529,8 +527,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param group InterpreterSetting reference name
|
||||
* @param properties
|
||||
* @param group InterpreterSetting reference name
|
||||
* @return
|
||||
*/
|
||||
public InterpreterSetting add(String group, ArrayList<InterpreterInfo> interpreterInfos,
|
||||
|
|
@ -579,8 +576,8 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
|
||||
} else {
|
||||
interpreterSetting =
|
||||
new InterpreterSetting(group, null, interpreterInfos, properties, dependencies,
|
||||
option, path);
|
||||
new InterpreterSetting(group, null, interpreterInfos, properties, dependencies, option,
|
||||
path);
|
||||
interpreterSettingsRef.put(group, interpreterSetting);
|
||||
}
|
||||
}
|
||||
|
|
@ -1193,6 +1190,16 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
return interpreters.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Support the legacy way to use it
|
||||
for (InterpreterSetting s : settings) {
|
||||
if (s.getGroup().equals(replName)) {
|
||||
List<Interpreter> interpreters = createOrGetInterpreterList(noteId, s);
|
||||
if (null != interpreters) {
|
||||
return interpreters.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dev interpreter
|
||||
|
|
|
|||
|
|
@ -411,24 +411,40 @@ public class Note implements Serializable, ParagraphJobListener {
|
|||
List<Map<String, String>> paragraphsInfo = new LinkedList<>();
|
||||
synchronized (paragraphs) {
|
||||
for (Paragraph p : paragraphs) {
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put("id", p.getId());
|
||||
info.put("status", p.getStatus().toString());
|
||||
if (p.getDateStarted() != null) {
|
||||
info.put("started", p.getDateStarted().toString());
|
||||
}
|
||||
if (p.getDateFinished() != null) {
|
||||
info.put("finished", p.getDateFinished().toString());
|
||||
}
|
||||
if (p.getStatus().isRunning()) {
|
||||
info.put("progress", String.valueOf(p.progress()));
|
||||
}
|
||||
Map<String, String> info = populatePragraphInfo(p);
|
||||
paragraphsInfo.add(info);
|
||||
}
|
||||
}
|
||||
return paragraphsInfo;
|
||||
}
|
||||
|
||||
public Map<String, String> generateSingleParagraphInfo(String paragraphId) {
|
||||
synchronized (paragraphs) {
|
||||
for (Paragraph p : paragraphs) {
|
||||
if (p.getId().equals(paragraphId)) {
|
||||
return populatePragraphInfo(p);
|
||||
}
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> populatePragraphInfo(Paragraph p) {
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put("id", p.getId());
|
||||
info.put("status", p.getStatus().toString());
|
||||
if (p.getDateStarted() != null) {
|
||||
info.put("started", p.getDateStarted().toString());
|
||||
}
|
||||
if (p.getDateFinished() != null) {
|
||||
info.put("finished", p.getDateFinished().toString());
|
||||
}
|
||||
if (p.getStatus().isRunning()) {
|
||||
info.put("progress", String.valueOf(p.progress()));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all paragraphs sequentially.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import org.apache.zeppelin.user.AuthenticationInfo;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
|
|
@ -191,20 +193,45 @@ public class ZeppelinHubRepo implements NotebookRepo {
|
|||
@Override
|
||||
public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationInfo subject)
|
||||
throws IOException {
|
||||
// Auto-generated method stub
|
||||
return null;
|
||||
if (StringUtils.isBlank(noteId)) {
|
||||
return null;
|
||||
}
|
||||
String endpoint = Joiner.on("/").join(noteId, "checkpoint");
|
||||
String content = GSON.toJson(ImmutableMap.of("message", checkpointMsg));
|
||||
String response = restApiClient.asyncPutWithResponseBody(endpoint, content);
|
||||
|
||||
return GSON.fromJson(response, Revision.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException {
|
||||
// Auto-generated method stub
|
||||
return null;
|
||||
if (StringUtils.isBlank(noteId) || StringUtils.isBlank(revId)) {
|
||||
return EMPTY_NOTE;
|
||||
}
|
||||
String endpoint = Joiner.on("/").join(noteId, "checkpoint", revId);
|
||||
String response = restApiClient.asyncGet(endpoint);
|
||||
Note note = GSON.fromJson(response, Note.class);
|
||||
if (note == null) {
|
||||
return EMPTY_NOTE;
|
||||
}
|
||||
LOG.info("ZeppelinHub REST API get note {} revision {}", noteId, revId);
|
||||
return note;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Revision> revisionHistory(String noteId, AuthenticationInfo subject) {
|
||||
// Auto-generated method stub
|
||||
return null;
|
||||
if (StringUtils.isBlank(noteId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String endpoint = Joiner.on("/").join(noteId, "checkpoint");
|
||||
List<Revision> history = Collections.emptyList();
|
||||
try {
|
||||
String response = restApiClient.asyncGet(endpoint);
|
||||
history = GSON.fromJson(response, new TypeToken<List<Revision>>(){}.getType());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Cannot get note history", e);
|
||||
}
|
||||
return history;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,8 @@ import java.util.concurrent.TimeoutException;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
|
@ -115,89 +114,66 @@ public class ZeppelinhubRestApiHandler {
|
|||
}
|
||||
|
||||
public String asyncGet(String argument) throws IOException {
|
||||
String note = StringUtils.EMPTY;
|
||||
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
client.newRequest(zepelinhubUrl + argument)
|
||||
.header(ZEPPELIN_TOKEN_HEADER, token)
|
||||
.send(listener);
|
||||
|
||||
// Wait for the response headers to arrive
|
||||
Response response;
|
||||
try {
|
||||
response = listener.get(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
LOG.error("Cannot perform Get request to ZeppelinHub", e);
|
||||
throw new IOException("Cannot load note from ZeppelinHub", e);
|
||||
}
|
||||
|
||||
int code = response.getStatus();
|
||||
if (code == 200) {
|
||||
try (InputStream responseContent = listener.getInputStream()) {
|
||||
note = IOUtils.toString(responseContent, "UTF-8");
|
||||
}
|
||||
} else {
|
||||
LOG.error("ZeppelinHub Get {} returned with status {} ", zepelinhubUrl + argument, code);
|
||||
throw new IOException("Cannot load note from ZeppelinHub");
|
||||
}
|
||||
return note;
|
||||
return sendToZeppelinHub(HttpMethod.GET, zepelinhubUrl + argument);
|
||||
}
|
||||
|
||||
|
||||
public String asyncPutWithResponseBody(String url, String json) throws IOException {
|
||||
if (StringUtils.isBlank(url) || StringUtils.isBlank(json)) {
|
||||
LOG.error("Empty note, cannot send it to zeppelinHub");
|
||||
throw new IOException("Cannot send emtpy note to zeppelinHub");
|
||||
}
|
||||
return sendToZeppelinHub(HttpMethod.PUT, zepelinhubUrl + url, json);
|
||||
}
|
||||
|
||||
public void asyncPut(String jsonNote) throws IOException {
|
||||
if (StringUtils.isBlank(jsonNote)) {
|
||||
LOG.error("Cannot save empty note/string to ZeppelinHub");
|
||||
return;
|
||||
}
|
||||
|
||||
client.newRequest(zepelinhubUrl).method(HttpMethod.PUT)
|
||||
.header(ZEPPELIN_TOKEN_HEADER, token)
|
||||
.content(new StringContentProvider(jsonNote, "UTF-8"), "application/json;charset=UTF-8")
|
||||
.send(new BufferingResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onComplete(Result res) {
|
||||
if (!res.isFailed() && res.getResponse().getStatus() == 200) {
|
||||
LOG.info("Successfully saved note to ZeppelinHub with {}",
|
||||
res.getResponse().getStatus());
|
||||
} else {
|
||||
LOG.warn("Failed to save note to ZeppelinHub with HttpStatus {}",
|
||||
res.getResponse().getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure) {
|
||||
LOG.error("Failed to save note to ZeppelinHub: {}", response.getReason(), failure);
|
||||
}
|
||||
});
|
||||
sendToZeppelinHub(HttpMethod.PUT, zepelinhubUrl, jsonNote);
|
||||
}
|
||||
|
||||
public void asyncDel(String argument) {
|
||||
public void asyncDel(String argument) throws IOException {
|
||||
if (StringUtils.isBlank(argument)) {
|
||||
LOG.error("Cannot delete empty note from ZeppelinHub");
|
||||
return;
|
||||
}
|
||||
client.newRequest(zepelinhubUrl + argument)
|
||||
.method(HttpMethod.DELETE)
|
||||
.header(ZEPPELIN_TOKEN_HEADER, token)
|
||||
.send(new BufferingResponseListener() {
|
||||
sendToZeppelinHub(HttpMethod.DELETE, zepelinhubUrl + argument);
|
||||
}
|
||||
|
||||
private String sendToZeppelinHub(HttpMethod method, String url) throws IOException {
|
||||
return sendToZeppelinHub(method, url, StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
private String sendToZeppelinHub(HttpMethod method, String url, String json) throws IOException {
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
Response response;
|
||||
String data;
|
||||
|
||||
@Override
|
||||
public void onComplete(Result res) {
|
||||
if (!res.isFailed() && res.getResponse().getStatus() == 200) {
|
||||
LOG.info("Successfully removed note from ZeppelinHub with {}",
|
||||
res.getResponse().getStatus());
|
||||
} else {
|
||||
LOG.warn("Failed to remove note from ZeppelinHub with HttpStatus {}",
|
||||
res.getResponse().getStatus());
|
||||
}
|
||||
}
|
||||
Request request = client.newRequest(url).method(method).header(ZEPPELIN_TOKEN_HEADER, token);
|
||||
if ((method.equals(HttpMethod.PUT) || method.equals(HttpMethod.POST)) &&
|
||||
!StringUtils.isBlank(json)) {
|
||||
request.content(new StringContentProvider(json, "UTF-8"), "application/json;charset=UTF-8");
|
||||
}
|
||||
request.send(listener);
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure) {
|
||||
LOG.error("Failed to remove note from ZeppelinHub: {}", response.getReason(), failure);
|
||||
}
|
||||
});
|
||||
try {
|
||||
response = listener.get(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
LOG.error("Cannot perform {} request to ZeppelinHub", method, e);
|
||||
throw new IOException("Cannot perform " + method + " request to ZeppelinHub", e);
|
||||
}
|
||||
|
||||
int code = response.getStatus();
|
||||
if (code == 200) {
|
||||
try (InputStream responseContent = listener.getInputStream()) {
|
||||
data = IOUtils.toString(responseContent, "UTF-8");
|
||||
}
|
||||
} else {
|
||||
LOG.error("ZeppelinHub {} {} returned with status {} ", method, url, code);
|
||||
throw new IOException("Cannot perform " + method + " request to ZeppelinHub");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
|
|
|||
|
|
@ -155,5 +155,6 @@ public class InterpreterFactoryTest {
|
|||
}});
|
||||
|
||||
assertEquals("className1", factory.getInterpreter("note", "test-group1").getClassName());
|
||||
assertEquals("className1", factory.getInterpreter("note", "group1").getClassName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue