mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2403
# Conflicts: # zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java # zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSettingManager.java
This commit is contained in:
commit
fd25c467ac
95 changed files with 3414 additions and 483 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -122,3 +122,6 @@ tramp
|
|||
|
||||
# tmp files
|
||||
/tmp/
|
||||
|
||||
# Git properties
|
||||
**/git.properties
|
||||
|
|
|
|||
|
|
@ -49,10 +49,9 @@ matrix:
|
|||
#
|
||||
# Several tests were excluded from this configuration due to the following issues:
|
||||
# HeliumApplicationFactoryTest - https://issues.apache.org/jira/browse/ZEPPELIN-2470
|
||||
# ZeppelinRestApiTest - https://issues.apache.org/jira/browse/ZEPPELIN-2473
|
||||
# After issues are fixed these tests need to be included back by removing them from the "-Dtests.to.exclude" property
|
||||
- jdk: "oraclejdk7"
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtests.to.exclude=**/ZeppelinSparkClusterTest.java,**/org.apache.zeppelin.spark.*,**/HeliumApplicationFactoryTest.java,**/ZeppelinRestApiTest.java -DfailIfNoTests=false"
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pscalding -Phelium-dev -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" MODULES="-pl ${INTERPRETERS}" TEST_PROJECTS="-Dtests.to.exclude=**/ZeppelinSparkClusterTest.java,**/org.apache.zeppelin.spark.*,**/HeliumApplicationFactoryTest.java -DfailIfNoTests=false"
|
||||
|
||||
# Test selenium with spark module for 1.6.3
|
||||
- jdk: "oraclejdk7"
|
||||
|
|
@ -60,7 +59,7 @@ matrix:
|
|||
|
||||
# Test interpreter modules
|
||||
- jdk: "oraclejdk7"
|
||||
env: SCALA_VER="2.10" PROFILE="-Pscalding" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl $(echo .,zeppelin-interpreter,${INTERPRETERS} | sed 's/!//g')" TEST_PROJECTS=""
|
||||
env: SCALA_VER="2.10" PROFILE="-Pscalding" BUILD_FLAG="package -DskipTests -DskipRat -Pr" TEST_FLAG="test -DskipRat" MODULES="-pl $(echo .,zeppelin-interpreter,${INTERPRETERS} | sed 's/!//g')" TEST_PROJECTS=""
|
||||
|
||||
# Test spark module for 2.1.0 with scala 2.11, livy
|
||||
- jdk: "oraclejdk7"
|
||||
|
|
@ -94,7 +93,7 @@ before_install:
|
|||
- changedfiles=$(git diff --name-only $TRAVIS_COMMIT_RANGE 2>/dev/null) || changedfiles=""
|
||||
- echo $changedfiles
|
||||
- hasbowerchanged=$(echo $changedfiles | grep -c "bower.json" || true);
|
||||
- gitlog=$(git log $TRAVIS_COMMIT_RANGE 2>/dev/null) || gitlog=""
|
||||
- gitlog=$(git log $TRAVIS_COMMIT_RANGE 2>/dev/null) || gitlog=""
|
||||
- clearcache=$(echo $gitlog | grep -c -E "clear bower|bower clear" || true)
|
||||
- if [ "$hasbowerchanged" -gt 0 ] || [ "$clearcache" -gt 0 ]; then echo "Clearing bower_components cache"; rm -r zeppelin-web/bower_components; npm cache clear; else echo "Using cached bower_components."; fi
|
||||
- echo "MAVEN_OPTS='-Xms1024M -Xmx2048M -XX:MaxPermSize=1024m -XX:-UseGCOverheadLimit -Dorg.slf4j.simpleLogger.defaultLogLevel=warn'" >> ~/.mavenrc
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
[users]
|
||||
# List of users with their password allowed to access Zeppelin.
|
||||
# To use a different strategy (LDAP / Database / ...) check the shiro doc at http://shiro.apache.org/configuration.html#Configuration-INISections
|
||||
admin = password1, admin
|
||||
# To enable admin user, uncomment the following line and set an appropriate password.
|
||||
#admin = password1, admin
|
||||
user1 = password2, role1, role2
|
||||
user2 = password3, role3
|
||||
user3 = password4, role2
|
||||
|
|
@ -73,14 +74,22 @@ role3 = *
|
|||
admin = *
|
||||
|
||||
[urls]
|
||||
# This section is used for url-based security.
|
||||
# You can secure interpreter, configuration and credential information by urls. Comment or uncomment the below urls that you want to hide.
|
||||
# This section is used for url-based security. For details see the shiro.ini documentation.
|
||||
#
|
||||
# You can secure interpreter, configuration and credential information by urls.
|
||||
# Comment or uncomment the below urls that you want to hide:
|
||||
# anon means the access is anonymous.
|
||||
# authc means Form based Auth Security
|
||||
# To enfore security, comment the line below and uncomment the next one
|
||||
# authc means form based auth Security.
|
||||
#
|
||||
# IMPORTANT: Order matters: URL path expressions are evaluated against an incoming request
|
||||
# in the order they are defined and the FIRST MATCH WINS.
|
||||
#
|
||||
# To allow anonymous access to all but the stated urls,
|
||||
# uncomment the line second last line (/** = anon) and comment the last line (/** = authc)
|
||||
#
|
||||
/api/version = anon
|
||||
#/api/interpreter/** = authc, roles[admin]
|
||||
#/api/configurations/** = authc, roles[admin]
|
||||
#/api/credential/** = authc, roles[admin]
|
||||
/api/interpreter/** = authc, roles[admin]
|
||||
/api/configurations/** = authc, roles[admin]
|
||||
/api/credential/** = authc, roles[admin]
|
||||
#/** = anon
|
||||
/** = authc
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ REM set ZEPPELIN_IDENT_STRING REM A string representing this instance of zep
|
|||
REM set ZEPPELIN_NICENESS REM The scheduling priority for daemons. Defaults to 0.
|
||||
REM set ZEPPELIN_INTERPRETER_LOCALREPO REM Local repository for interpreter's additional dependency loading
|
||||
REM set ZEPPELIN_INTERPRETER_DEP_MVNREPO REM Maven principal repository for interpreter's additional dependency loading
|
||||
REM set ZEPPELIN_HELIUM_NPM_REGISTRY REM Remote Npm registry for Helium dependency loader
|
||||
REM set ZEPPELIN_HELIUM_NODE_INSTALLER_URL REM Remote Node installer url for Helium dependency loader
|
||||
REM set ZEPPELIN_HELIUM_NPM_INSTALLER_URL REM Remote Npm installer url for Helium dependency loader
|
||||
REM set ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL REM Remote Yarn package installer url for Helium dependency loader
|
||||
REM set ZEPPELIN_NOTEBOOK_STORAGE REM Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote).
|
||||
REM set ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC REM If there are multiple notebook storages, should we treat the first one as the only source of truth?
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@
|
|||
# export ZEPPELIN_NICENESS # The scheduling priority for daemons. Defaults to 0.
|
||||
# export ZEPPELIN_INTERPRETER_LOCALREPO # Local repository for interpreter's additional dependency loading
|
||||
# export ZEPPELIN_INTERPRETER_DEP_MVNREPO # Remote principal repository for interpreter's additional dependency loading
|
||||
# export ZEPPELIN_HELIUM_NPM_REGISTRY # Remote Npm registry for Helium dependency loader
|
||||
# export ZEPPELIN_HELIUM_NODE_INSTALLER_URL # Remote Node installer url for Helium dependency loader
|
||||
# export ZEPPELIN_HELIUM_NPM_INSTALLER_URL # Remote Npm installer url for Helium dependency loader
|
||||
# export ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL # Remote Yarn package installer url for Helium dependency loader
|
||||
# export ZEPPELIN_NOTEBOOK_STORAGE # Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote).
|
||||
# export ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC # If there are multiple notebook storages, should we treat the first one as the only source of truth?
|
||||
# export ZEPPELIN_NOTEBOOK_PUBLIC # Make notebook public by default when created, private otherwise
|
||||
|
|
|
|||
|
|
@ -252,9 +252,21 @@
|
|||
</property>
|
||||
|
||||
<property>
|
||||
<name>zeppelin.helium.npm.registry</name>
|
||||
<name>zeppelin.helium.node.installer.url</name>
|
||||
<value>https://nodejs.org/dist/</value>
|
||||
<description>Remote Node installer url for Helium dependency loader</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>zeppelin.helium.npm.installer.url</name>
|
||||
<value>http://registry.npmjs.org/</value>
|
||||
<description>Remote Npm registry for Helium dependency loader</description>
|
||||
<description>Remote Npm installer url for Helium dependency loader</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>zeppelin.helium.yarnpkg.installer.url</name>
|
||||
<value>https://github.com/yarnpkg/yarn/releases/download/</value>
|
||||
<description>Remote Yarn package installer url for Helium dependency loader</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
<li><a href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#text">Text</a></li>
|
||||
<li><a href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#html">Html</a></li>
|
||||
<li><a href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#table">Table</a></li>
|
||||
<li><a href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#network">Network</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="title"><span><b>Angular API</b><span></li>
|
||||
<li><a href="{{BASE_PATH}}/displaysystem/back-end-angular.html">Angular (backend API)</a></li>
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/assets/themes/zeppelin/img/screenshots/display_network.png
Normal file
BIN
docs/assets/themes/zeppelin/img/screenshots/display_network.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
|
|
@ -61,3 +61,104 @@ If table contents start with `%html`, it is interpreted as an HTML.
|
|||
<img src="../assets/themes/zeppelin/img/screenshots/display_table_html.png" />
|
||||
|
||||
> **Note :** Display system is backend independent.
|
||||
|
||||
## Network
|
||||
|
||||
With the `%network` directive, Zeppelin treats your output as a graph. Zeppelin can leverage the Property Graph Model.
|
||||
|
||||
### What is the Labelled Property Graph Model?
|
||||
|
||||
A [Property Graph](https://github.com/tinkerpop/gremlin/wiki/Defining-a-Property-Graph) is a graph that has these elements:
|
||||
|
||||
* a set of vertices
|
||||
* each vertex has a unique identifier.
|
||||
* each vertex has a set of outgoing edges.
|
||||
* each vertex has a set of incoming edges.
|
||||
* each vertex has a collection of properties defined by a map from key to value
|
||||
* a set of edges
|
||||
* each edge has a unique identifier.
|
||||
* each edge has an outgoing tail vertex.
|
||||
* each edge has an incoming head vertex.
|
||||
* each edge has a label that denotes the type of relationship between its two vertices.
|
||||
* each edge has a collection of properties defined by a map from key to value.
|
||||
|
||||
<img src="https://github.com/tinkerpop/gremlin/raw/master/doc/images/graph-example-1.jpg" />
|
||||
|
||||
A [Labelled Property Graph](https://neo4j.com/developer/graph-database/#property-graph) is a Property Graph where the nodes can be tagged with **labels** representing their different roles in the graph model
|
||||
|
||||
<img src="http://s3.amazonaws.com/dev.assets.neo4j.com/wp-content/uploads/property_graph_model.png" />
|
||||
|
||||
### What are the APIs?
|
||||
|
||||
The new NETWORK visualization is based on json with the following params:
|
||||
|
||||
* "nodes" (mandatory): list of nodes of the graph every node can have the following params:
|
||||
* "id" (mandatory): the id of the node (must be unique);
|
||||
* "label": the main Label of the node;
|
||||
* "labels": the list of the labels of the node;
|
||||
* "data": the data attached to the node;
|
||||
* "edges": list of the edges of the graph;
|
||||
* "id" (mandatory): the id of the edge (must be unique);
|
||||
* "source" (mandatory): the id of source node of the edge;
|
||||
* "target" (mandatory): the id of target node of the edge;
|
||||
* "label": the main type of the edge;
|
||||
* "data": the data attached to the edge;
|
||||
* "labels": a map (K, V) where K is the node label and V is the color of the node;
|
||||
* "directed": (true/false, default false) wich tells if is directed graph or not;
|
||||
* "types": a *distinct* list of the edge types of the graph
|
||||
|
||||
If you click on a node or edge on the bottom of the paragraph you find a list of entity properties
|
||||
|
||||
<img src="../assets/themes/zeppelin/img/screenshots/display_network.png" />
|
||||
|
||||
This kind of graph can be easily *flatten* in order to support other visualization formats provided by Zeppelin.
|
||||
|
||||
<img src="../assets/themes/zeppelin/img/screenshots/display_network_flatten.png" />
|
||||
|
||||
### How to use it?
|
||||
|
||||
An example of a simple graph
|
||||
|
||||
```
|
||||
%spark
|
||||
print(s"""
|
||||
%network {
|
||||
"nodes": [
|
||||
{"id": 1},
|
||||
{"id": 2},
|
||||
{"id": 3}
|
||||
],
|
||||
"edges": [
|
||||
{"source": 1, "target": 2, "id" : 1},
|
||||
{"source": 2, "target": 3, "id" : 2},
|
||||
{"source": 1, "target": 2, "id" : 3},
|
||||
{"source": 1, "target": 2, "id" : 4},
|
||||
{"source": 2, "target": 1, "id" : 5},
|
||||
{"source": 2, "target": 1, "id" : 6}
|
||||
]
|
||||
}
|
||||
""")
|
||||
```
|
||||
|
||||
that will look like:
|
||||
|
||||
<img src="../assets/themes/zeppelin/img/screenshots/display_simple_network.png" />
|
||||
|
||||
A little more complex graph:
|
||||
|
||||
```
|
||||
%spark
|
||||
print(s"""
|
||||
%network {
|
||||
"nodes": [{"id": 1, "label": "User", "data": {"fullName":"Andrea Santurbano"}},{"id": 2, "label": "User", "data": {"fullName":"Lee Moon Soo"}},{"id": 3, "label": "Project", "data": {"name":"Zeppelin"}}],
|
||||
"edges": [{"source": 2, "target": 1, "id" : 1, "label": "HELPS"},{"source": 2, "target": 3, "id" : 2, "label": "CREATE"},{"source": 1, "target": 3, "id" : 3, "label": "CONTRIBUTE_TO", "data": {"oldPR": "https://github.com/apache/zeppelin/pull/1582"}}],
|
||||
"labels": {"User": "#8BC34A", "Project": "#3071A9"},
|
||||
"directed": true,
|
||||
"types": ["HELPS", "CREATE", "CONTRIBUTE_TO"]
|
||||
}
|
||||
""")
|
||||
```
|
||||
|
||||
that will look like:
|
||||
|
||||
<img src="../assets/themes/zeppelin/img/screenshots/display_complex_network.png" />
|
||||
|
|
@ -282,10 +282,22 @@ If both are defined, then the **environment variables** will take priority.
|
|||
<td>Local repository for dependency loader.<br>ex)visualiztion modules of npm.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h6 class="properties">ZEPPELIN_HELIUM_NPM_REGISTRY</h6></td>
|
||||
<td><h6 class="properties">zeppelin.helium.npm.registry</h6></td>
|
||||
<td><h6 class="properties">ZEPPELIN_HELIUM_NODE_INSTALLER_URL</h6></td>
|
||||
<td><h6 class="properties">zeppelin.helium.node.installer.url</h6></td>
|
||||
<td>https://nodejs.org/dist/</td>
|
||||
<td>Remote Node installer url for Helium dependency loader</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h6 class="properties">ZEPPELIN_HELIUM_NPM_INSTALLER_URL</h6></td>
|
||||
<td><h6 class="properties">zeppelin.helium.npm.installer.url</h6></td>
|
||||
<td>http://registry.npmjs.org/</td>
|
||||
<td>Remote Npm registry for Helium dependency loader</td>
|
||||
<td>Remote Npm installer url for Helium dependency loader</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h6 class="properties">ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL</h6></td>
|
||||
<td><h6 class="properties">zeppelin.helium.yarnpkg.installer.url</h6></td>
|
||||
<td>https://github.com/yarnpkg/yarn/releases/download/</td>
|
||||
<td>Remote Yarn package installer url for Helium dependency loader</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h6 class="properties">ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE</h6></td>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,45 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
|
||||
## Zeppelin Server REST API list
|
||||
|
||||
### Get Zeppelin version
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns Zeppelin version</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/version```</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",
|
||||
"message": "Zeppelin version",
|
||||
"body": [
|
||||
{
|
||||
"version": "0.8.0",
|
||||
"git-commit-id": "abc0123",
|
||||
"git-timestamp": "2017-01-02 03:04:05"
|
||||
}
|
||||
]
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Change the log level of Zeppelin Server
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
|
|
@ -49,29 +88,27 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 406 </td>
|
||||
<td>Fail code</td>
|
||||
<td>406</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> sample JSON response
|
||||
</td>
|
||||
<td>sample JSON response</td>
|
||||
<td>
|
||||
<pre>
|
||||
{
|
||||
"status": "OK"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> sample error JSON response
|
||||
</td>
|
||||
<td>sample error JSON response</td>
|
||||
<td>
|
||||
<pre>
|
||||
{
|
||||
"status":"NOT_ACCEPTABLE",
|
||||
"message":"Please check LOG level specified. Valid values: DEBUG, ERROR, FATAL, INFO, TRACE, WARN"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -341,7 +341,17 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
private InterpreterResult getResultFromStatementInfo(StatementInfo stmtInfo,
|
||||
boolean displayAppInfo) {
|
||||
if (stmtInfo.output != null && stmtInfo.output.isError()) {
|
||||
return new InterpreterResult(InterpreterResult.Code.ERROR, stmtInfo.output.evalue);
|
||||
InterpreterResult result = new InterpreterResult(InterpreterResult.Code.ERROR);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(stmtInfo.output.evalue);
|
||||
// in case evalue doesn't have newline char
|
||||
if (!stmtInfo.output.evalue.contains("\n"))
|
||||
sb.append("\n");
|
||||
if (stmtInfo.output.traceback != null) {
|
||||
sb.append(StringUtils.join(stmtInfo.output.traceback));
|
||||
}
|
||||
result.add(sb.toString());
|
||||
return result;
|
||||
} else if (stmtInfo.isCancelled()) {
|
||||
// corner case, output might be null if it is cancelled.
|
||||
return new InterpreterResult(InterpreterResult.Code.ERROR, "Job is cancelled");
|
||||
|
|
@ -659,7 +669,18 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
public static StatementInfo fromJson(String json) {
|
||||
return gson.fromJson(json, StatementInfo.class);
|
||||
String right_json = "";
|
||||
try {
|
||||
gson.fromJson(json, StatementInfo.class);
|
||||
right_json = json;
|
||||
} catch (Exception e) {
|
||||
if (json.contains("\"traceback\":{}")) {
|
||||
LOGGER.debug("traceback type mismatch, replacing the mismatching part ");
|
||||
right_json = json.replace("\"traceback\":{}", "\"traceback\":[]");
|
||||
LOGGER.debug("new json string is {}", right_json);
|
||||
}
|
||||
}
|
||||
return gson.fromJson(right_json, StatementInfo.class);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
|
|
@ -676,7 +697,7 @@ public abstract class BaseLivyInterpreter extends Interpreter {
|
|||
public Data data;
|
||||
public String ename;
|
||||
public String evalue;
|
||||
public Object traceback;
|
||||
public String[] traceback;
|
||||
public TableMagic tableMagic;
|
||||
|
||||
public boolean isError() {
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ public class LivyInterpreterIT {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testPySparkInterpreter() {
|
||||
public void testPySparkInterpreter() throws LivyException {
|
||||
if (!checkPreCondition()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -536,6 +536,24 @@ public class LivyInterpreterIT {
|
|||
"title", "text", authInfo, null, null, null, null, null, output);
|
||||
pysparkInterpreter.open();
|
||||
|
||||
// test traceback msg
|
||||
try {
|
||||
pysparkInterpreter.getLivyVersion();
|
||||
// for livy version >=0.3 , input some erroneous spark code, check the shown result is more than one line
|
||||
InterpreterResult result = pysparkInterpreter.interpret("sc.parallelize(wrongSyntax(1, 2)).count()", context);
|
||||
assertEquals(InterpreterResult.Code.ERROR, result.code());
|
||||
assertTrue(result.message().get(0).getData().split("\n").length>1);
|
||||
assertTrue(result.message().get(0).getData().contains("Traceback"));
|
||||
} catch (APINotFoundException e) {
|
||||
// only livy 0.2 can throw this exception since it doesn't have /version endpoint
|
||||
// in livy 0.2, most error msg is encapsulated in evalue field, only print(a) in pyspark would return none-empty
|
||||
// traceback
|
||||
InterpreterResult result = pysparkInterpreter.interpret("print(a)", context);
|
||||
assertEquals(InterpreterResult.Code.ERROR, result.code());
|
||||
assertTrue(result.message().get(0).getData().split("\n").length>1);
|
||||
assertTrue(result.message().get(0).getData().contains("Traceback"));
|
||||
}
|
||||
|
||||
try {
|
||||
InterpreterResult result = pysparkInterpreter.interpret("sc.version", context);
|
||||
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
|
||||
|
|
@ -763,6 +781,7 @@ public class LivyInterpreterIT {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isSpark2(BaseLivyInterpreter interpreter, InterpreterContext context) {
|
||||
InterpreterResult result = null;
|
||||
if (interpreter instanceof LivySparkRInterpreter) {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
"msg": [
|
||||
{
|
||||
"type": "TEXT",
|
||||
"data": "\nimport org.apache.commons.io.IOUtils\n\nimport java.net.URL\n\nimport java.nio.charset.Charset\n\nbankText: org.apache.spark.rdd.RDD[String] \u003d ParallelCollectionRDD[0] at parallelize at \u003cconsole\u003e:32\n\ndefined class Bank\n\nbank: org.apache.spark.sql.DataFrame \u003d [age: int, job: string ... 3 more fields]\n\nwarning: there were 1 deprecation warning(s); re-run with -deprecation for details\n"
|
||||
"data": "import org.apache.commons.io.IOUtils\nimport java.net.URL\nimport java.nio.charset.Charset\nbankText: org.apache.spark.rdd.RDD[String] \u003d ParallelCollectionRDD[36] at parallelize at \u003cconsole\u003e:43\ndefined class Bank\nbank: org.apache.spark.sql.DataFrame \u003d [age: int, job: string ... 3 more fields]\nwarning: there were 1 deprecation warning(s); re-run with -deprecation for details\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
29
pom.xml
29
pom.xml
|
|
@ -78,6 +78,7 @@
|
|||
<module>scio</module>
|
||||
<module>zeppelin-web</module>
|
||||
<module>zeppelin-server</module>
|
||||
<module>zeppelin-jupyter</module>
|
||||
<module>zeppelin-distribution</module>
|
||||
</modules>
|
||||
|
||||
|
|
@ -105,6 +106,7 @@
|
|||
<commons.io.version>2.4</commons.io.version>
|
||||
<commons.collections.version>3.2.1</commons.collections.version>
|
||||
<commons.logging.version>1.1.1</commons.logging.version>
|
||||
<commons.cli.version>1.3.1</commons.cli.version>
|
||||
<shiro.version>1.2.3</shiro.version>
|
||||
|
||||
<!-- test library versions -->
|
||||
|
|
@ -232,6 +234,12 @@
|
|||
<version>${commons.logging.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>${commons.cli.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
@ -519,6 +527,27 @@
|
|||
<version>${plugin.deploy.version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<skipPoms>false</skipPoms>
|
||||
<dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
|
||||
<generateGitPropertiesFile>true</generateGitPropertiesFile>
|
||||
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
|
||||
<failOnNoGitDirectory>false</failOnNoGitDirectory>
|
||||
<dateFormat>yyyy-MM-dd HH:mm:ss</dateFormat>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!--TODO(alex): make part of the build and reconcile conflicts
|
||||
<plugin>
|
||||
<groupId>com.ning.maven.plugins</groupId>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.util._
|
|||
|
||||
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||
import org.apache.zeppelin.interpreter.Interpreter.FormType
|
||||
import org.apache.zeppelin.interpreter.remote.RemoteInterpreter
|
||||
import org.apache.zeppelin.interpreter.{InterpreterContext, _}
|
||||
import org.apache.zeppelin.scheduler.Scheduler
|
||||
import org.apache.zeppelin.spark.SparkInterpreter
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class InterpreterOption {
|
|||
|
||||
boolean isExistingProcess;
|
||||
boolean setPermission;
|
||||
List<String> users;
|
||||
List<String> owners;
|
||||
boolean isUserImpersonate;
|
||||
|
||||
public boolean isExistingProcess() {
|
||||
|
|
@ -64,8 +64,8 @@ public class InterpreterOption {
|
|||
this.setPermission = setPermission;
|
||||
}
|
||||
|
||||
public List<String> getUsers() {
|
||||
return users;
|
||||
public List<String> getOwners() {
|
||||
return owners;
|
||||
}
|
||||
|
||||
public boolean isUserImpersonate() {
|
||||
|
|
@ -106,8 +106,8 @@ public class InterpreterOption {
|
|||
option.perUser = other.perUser;
|
||||
option.isExistingProcess = other.isExistingProcess;
|
||||
option.setPermission = other.setPermission;
|
||||
option.users = (null == other.users) ?
|
||||
new ArrayList<String>() : new ArrayList<>(other.users);
|
||||
option.owners = (null == other.owners) ?
|
||||
new ArrayList<String>() : new ArrayList<>(other.owners);
|
||||
|
||||
return option;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ public class InterpreterResult implements Serializable {
|
|||
TABLE,
|
||||
IMG,
|
||||
SVG,
|
||||
NULL
|
||||
NULL,
|
||||
NETWORK
|
||||
}
|
||||
|
||||
Code code;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.apache.zeppelin.user;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -28,9 +32,10 @@ import org.slf4j.LoggerFactory;
|
|||
public class AuthenticationInfo {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationInfo.class);
|
||||
String user;
|
||||
List<String> roles;
|
||||
String ticket;
|
||||
UserCredentials userCredentials;
|
||||
public static final AuthenticationInfo ANONYMOUS = new AuthenticationInfo("anonymous",
|
||||
public static final AuthenticationInfo ANONYMOUS = new AuthenticationInfo("anonymous", null,
|
||||
"anonymous");
|
||||
|
||||
public AuthenticationInfo() {}
|
||||
|
|
@ -44,9 +49,13 @@ public class AuthenticationInfo {
|
|||
* @param user
|
||||
* @param ticket
|
||||
*/
|
||||
public AuthenticationInfo(String user, String ticket) {
|
||||
public AuthenticationInfo(String user, String roles, String ticket) {
|
||||
this.user = user;
|
||||
this.ticket = ticket;
|
||||
if (StringUtils.isNotBlank(roles) && roles.length() > 2) {
|
||||
String[] r = roles.substring(1, roles.length() - 1).split(",");
|
||||
this.roles = Arrays.asList(r);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
|
|
@ -57,6 +66,26 @@ public class AuthenticationInfo {
|
|||
this.user = user;
|
||||
}
|
||||
|
||||
public List<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public List<String> getUsersAndRoles() {
|
||||
List<String> usersAndRoles = new ArrayList<>();
|
||||
if (roles != null) {
|
||||
usersAndRoles.addAll(roles);
|
||||
}
|
||||
if (user != null) {
|
||||
usersAndRoles.add(user);
|
||||
}
|
||||
|
||||
return usersAndRoles;
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public class InterpreterTest {
|
|||
null,
|
||||
paragraphTitle,
|
||||
paragraphText,
|
||||
new AuthenticationInfo("testUser", "testTicket"),
|
||||
new AuthenticationInfo("testUser", null, "testTicket"),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
|
|
|||
64
zeppelin-jupyter/pom.xml
Normal file
64
zeppelin-jupyter/pom.xml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>zeppelin</artifactId>
|
||||
<groupId>org.apache.zeppelin</groupId>
|
||||
<version>0.8.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>zeppelin-jupyter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.8.0-SNAPSHOT</version>
|
||||
<name>Zeppelin: Jupyter Support</name>
|
||||
<description>Jupyter support for Apache Zeppelin</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.danilopianini</groupId>
|
||||
<artifactId>gson-extras</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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.jupyter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
import org.apache.zeppelin.jupyter.nbformat.Cell;
|
||||
import org.apache.zeppelin.jupyter.nbformat.CodeCell;
|
||||
import org.apache.zeppelin.jupyter.nbformat.DisplayData;
|
||||
import org.apache.zeppelin.jupyter.nbformat.Error;
|
||||
import org.apache.zeppelin.jupyter.nbformat.ExecuteResult;
|
||||
import org.apache.zeppelin.jupyter.nbformat.HeadingCell;
|
||||
import org.apache.zeppelin.jupyter.nbformat.MarkdownCell;
|
||||
import org.apache.zeppelin.jupyter.nbformat.Nbformat;
|
||||
import org.apache.zeppelin.jupyter.nbformat.Output;
|
||||
import org.apache.zeppelin.jupyter.nbformat.RawCell;
|
||||
import org.apache.zeppelin.jupyter.nbformat.Stream;
|
||||
import org.apache.zeppelin.jupyter.zformat.Note;
|
||||
import org.apache.zeppelin.jupyter.zformat.Paragraph;
|
||||
import org.apache.zeppelin.jupyter.zformat.Result;
|
||||
import org.apache.zeppelin.jupyter.zformat.TypeData;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class JupyterUtil {
|
||||
|
||||
private static final String TEXT_PLAIN = "text/plain";
|
||||
private static final String IMAGE_PNG = "image/png";
|
||||
|
||||
private final RuntimeTypeAdapterFactory<Cell> cellTypeFactory;
|
||||
private final RuntimeTypeAdapterFactory<Output> outputTypeFactory;
|
||||
|
||||
public JupyterUtil() {
|
||||
this.cellTypeFactory = RuntimeTypeAdapterFactory.of(Cell.class, "cell_type")
|
||||
.registerSubtype(MarkdownCell.class, "markdown").registerSubtype(CodeCell.class, "code")
|
||||
.registerSubtype(RawCell.class, "raw").registerSubtype(HeadingCell.class, "heading");
|
||||
this.outputTypeFactory = RuntimeTypeAdapterFactory.of(Output.class, "output_type")
|
||||
.registerSubtype(ExecuteResult.class, "execute_result")
|
||||
.registerSubtype(DisplayData.class, "display_data").registerSubtype(Stream.class, "stream")
|
||||
.registerSubtype(Error.class, "error");
|
||||
}
|
||||
|
||||
public Nbformat getNbformat(Reader in) {
|
||||
return getNbformat(in, new GsonBuilder());
|
||||
}
|
||||
|
||||
public Nbformat getNbformat(Reader in, GsonBuilder gsonBuilder) {
|
||||
return getGson(gsonBuilder).fromJson(in, Nbformat.class);
|
||||
}
|
||||
|
||||
public Note getNote(Reader in, String codeReplaced, String markdownReplaced) {
|
||||
return getNote(in, new GsonBuilder(), codeReplaced, markdownReplaced);
|
||||
}
|
||||
|
||||
public Note getNote(Reader in, GsonBuilder gsonBuilder, String codeReplaced,
|
||||
String markdownReplaced) {
|
||||
return getNote(getNbformat(in, gsonBuilder), codeReplaced, markdownReplaced);
|
||||
}
|
||||
|
||||
public Note getNote(Nbformat nbformat, String codeReplaced, String markdownReplaced) {
|
||||
Note note = new Note();
|
||||
|
||||
String name = nbformat.getMetadata().getTitle();
|
||||
if (null == name) {
|
||||
name = "Note converted from Jupyter";
|
||||
}
|
||||
note.setName(name);
|
||||
|
||||
String lineSeparator = System.lineSeparator();
|
||||
Paragraph paragraph;
|
||||
List<Paragraph> paragraphs = new ArrayList<>();
|
||||
String interpreterName;
|
||||
List<TypeData> typeDataList;
|
||||
String type;
|
||||
String result;
|
||||
|
||||
for (Cell cell : nbformat.getCells()) {
|
||||
paragraph = new Paragraph();
|
||||
typeDataList = new ArrayList<>();
|
||||
|
||||
if (cell instanceof CodeCell) {
|
||||
interpreterName = codeReplaced;
|
||||
for (Output output : ((CodeCell) cell).getOutputs()) {
|
||||
TypeData typeData;
|
||||
if (output instanceof Stream) {
|
||||
type = TypeData.TEXT;
|
||||
result = Joiner.on(lineSeparator).join(((Stream) output).getText());
|
||||
typeData = new TypeData(type, result);
|
||||
typeDataList.add(typeData);
|
||||
} else if (output instanceof ExecuteResult || output instanceof DisplayData) {
|
||||
Map<String, Object> data = (output instanceof ExecuteResult) ?
|
||||
((ExecuteResult) output).getData() :
|
||||
((DisplayData) output).getData();
|
||||
for (Map.Entry<String, Object> datum : data.entrySet()) {
|
||||
if (TEXT_PLAIN.equals(datum.getKey())) {
|
||||
type = TypeData.TEXT;
|
||||
result = Joiner.on(lineSeparator).join((List<String>) datum.getValue());
|
||||
} else if (IMAGE_PNG.equals(datum.getKey())) {
|
||||
type = TypeData.HTML;
|
||||
result = makeHTML(((String) datum.getValue()).replace("\n", ""));
|
||||
} else {
|
||||
type = TypeData.TEXT;
|
||||
result = datum.getValue().toString();
|
||||
}
|
||||
typeData = new TypeData(type, result);
|
||||
typeDataList.add(typeData);
|
||||
}
|
||||
} else {
|
||||
// Error
|
||||
Error error = (Error) output;
|
||||
type = TypeData.TEXT;
|
||||
result =
|
||||
Joiner.on(lineSeparator).join(new String[] {error.getEname(), error.getEvalue()});
|
||||
typeData = new TypeData(type, result);
|
||||
typeDataList.add(typeData);
|
||||
}
|
||||
}
|
||||
} else if (cell instanceof MarkdownCell || cell instanceof HeadingCell) {
|
||||
interpreterName = markdownReplaced;
|
||||
} else {
|
||||
interpreterName = "";
|
||||
}
|
||||
|
||||
paragraph.setText(
|
||||
interpreterName + lineSeparator + Joiner.on(lineSeparator).join(cell.getSource()));
|
||||
paragraph.setResults(new Result(Result.SUCCESS, typeDataList));
|
||||
|
||||
paragraphs.add(paragraph);
|
||||
}
|
||||
|
||||
note.setParagraphs(paragraphs);
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
private Gson getGson(GsonBuilder gsonBuilder) {
|
||||
return gsonBuilder.registerTypeAdapterFactory(cellTypeFactory)
|
||||
.registerTypeAdapterFactory(outputTypeFactory).create();
|
||||
}
|
||||
|
||||
private String makeHTML(String image) {
|
||||
return "<div style='width:auto;height:auto'><img src=data:image/png;base64," + image
|
||||
+ " style='width=auto;height:auto'/></div>";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws ParseException, IOException {
|
||||
Options options = new Options();
|
||||
options.addOption("i", true, "Jupyter notebook file");
|
||||
options.addOption("o", true, "Zeppelin note file. Default: note.json");
|
||||
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd = parser.parse(options, args);
|
||||
|
||||
if (!cmd.hasOption("i")) {
|
||||
new HelpFormatter().printHelp("java " + JupyterUtil.class.getName(), options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Path jupyterPath = Paths.get(cmd.getOptionValue("i"));
|
||||
Path zeppelinPath = Paths.get(cmd.hasOption("o") ? cmd.getOptionValue("o") : "note.json");
|
||||
|
||||
try (BufferedReader in = new BufferedReader(new FileReader(jupyterPath.toFile()));
|
||||
FileWriter fw = new FileWriter(zeppelinPath.toFile())) {
|
||||
Note note = new JupyterUtil().getNote(in, "python", "md");
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
gson.toJson(note, fw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Author {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
|
@ -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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Cell {
|
||||
|
||||
@SerializedName("cell_type")
|
||||
private String cellType;
|
||||
|
||||
@SerializedName("metadata")
|
||||
private CellMetadata metadata;
|
||||
|
||||
@SerializedName("source")
|
||||
private List<String> source;
|
||||
|
||||
public String getCellType() {
|
||||
return cellType;
|
||||
}
|
||||
|
||||
public CellMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public List<String> getSource() {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CellMetadata {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("tags")
|
||||
private List<String> tags;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CodeCell extends Cell {
|
||||
|
||||
@SerializedName("outputs")
|
||||
private List<Output> outputs;
|
||||
|
||||
public List<Output> getOutputs() {
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class DisplayData extends Output {
|
||||
|
||||
@SerializedName("data")
|
||||
private Map<String, Object> data;
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.zeppelin.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Error extends Output {
|
||||
@SerializedName("ename")
|
||||
private String ename;
|
||||
|
||||
@SerializedName("evalue")
|
||||
private String evalue;
|
||||
|
||||
@SerializedName("traceback")
|
||||
private List<String> traceback;
|
||||
|
||||
public String getEname() {
|
||||
return ename;
|
||||
}
|
||||
|
||||
public String getEvalue() {
|
||||
return evalue;
|
||||
}
|
||||
|
||||
public List<String> getTraceback() {
|
||||
return traceback;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExecuteResult extends Output {
|
||||
|
||||
@SerializedName("execution_count")
|
||||
private int executionCount;
|
||||
|
||||
@SerializedName("data")
|
||||
private Map<String, Object> data;
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HeadingCell extends Cell {
|
||||
|
||||
@SerializedName("level")
|
||||
private int level;
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Kernelspec {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("display_name")
|
||||
private String displayName;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class LanguageInfo {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("codemirror_mode")
|
||||
private Object codemirrorMode;
|
||||
|
||||
@SerializedName("file_extension")
|
||||
private String fileExtension;
|
||||
|
||||
@SerializedName("mimetype")
|
||||
private String mimetype;
|
||||
|
||||
@SerializedName("pygments_lexer")
|
||||
private String pygmentsLexer;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MarkdownCell extends Cell {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Metadata {
|
||||
|
||||
@SerializedName("kernelspec")
|
||||
private Kernelspec kernelspec;
|
||||
|
||||
@SerializedName("language_info")
|
||||
private LanguageInfo languageInfo;
|
||||
|
||||
@SerializedName("orig_nbformat")
|
||||
private int origNbformat;
|
||||
|
||||
@SerializedName("title")
|
||||
private String title;
|
||||
|
||||
@SerializedName("authors")
|
||||
private List<Author> authors;
|
||||
|
||||
public Kernelspec getKernelspec() {
|
||||
return kernelspec;
|
||||
}
|
||||
|
||||
public LanguageInfo getLanguageInfo() {
|
||||
return languageInfo;
|
||||
}
|
||||
|
||||
public int getOrigNbformat() {
|
||||
return origNbformat;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public List<Author> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Nbformat {
|
||||
|
||||
@SerializedName("metadata")
|
||||
private Metadata metadata;
|
||||
|
||||
@SerializedName("nbformat")
|
||||
private int nbformat;
|
||||
|
||||
@SerializedName("nbformat_minor")
|
||||
private int nbformatMinor;
|
||||
|
||||
@SerializedName("cells")
|
||||
private List<Cell> cells;
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public int getNbformat() {
|
||||
return nbformat;
|
||||
}
|
||||
|
||||
public int getNbformatMinor() {
|
||||
return nbformatMinor;
|
||||
}
|
||||
|
||||
public List<Cell> getCells() {
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Output {
|
||||
|
||||
@SerializedName("output_type")
|
||||
private String outputType;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RawCell extends Cell {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RawCellMetadata extends CellMetadata {
|
||||
|
||||
@SerializedName("format")
|
||||
private String format;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Stream extends Output {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("text")
|
||||
private List<String> text;
|
||||
|
||||
public List<String> getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.jupyter.zformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Note {
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("paragraphs")
|
||||
private List<Paragraph> paragraphs;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<Paragraph> getParagraphs() {
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
public void setParagraphs(List<Paragraph> paragraphs) {
|
||||
this.paragraphs = paragraphs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.jupyter.zformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Paragraph {
|
||||
public static final String FINISHED = "FINISHED";
|
||||
|
||||
@SerializedName("text")
|
||||
private String text;
|
||||
|
||||
@SerializedName("results")
|
||||
private Result results; // It's a bit weird name
|
||||
|
||||
@SerializedName("id")
|
||||
private String id;
|
||||
|
||||
@SerializedName("status")
|
||||
private String status;
|
||||
|
||||
public Paragraph() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
this.id = dateFormat.format(new Date()) + "_" + super.hashCode();
|
||||
this.status = FINISHED;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Result getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public void setResults(Result results) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.zeppelin.jupyter.zformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Result {
|
||||
public static final String SUCCESS = "SUCCESS";
|
||||
|
||||
@SerializedName("code")
|
||||
private String code;
|
||||
|
||||
@SerializedName("msg")
|
||||
private List<TypeData> msg;
|
||||
|
||||
public Result(String code, List<TypeData> msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public List<TypeData> getMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.jupyter.zformat;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TypeData {
|
||||
public static final String TABLE = "TABLE";
|
||||
public static final String HTML = "HTML";
|
||||
public static final String TEXT = "TEXT";
|
||||
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
|
||||
@SerializedName("data")
|
||||
private String data;
|
||||
|
||||
public TypeData(String type, String data) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.jupyter.nbformat;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import org.apache.zeppelin.jupyter.JupyterUtil;
|
||||
import org.apache.zeppelin.jupyter.zformat.Note;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class JupyterUtilTest {
|
||||
|
||||
@Test
|
||||
public void getNbFormat() {
|
||||
InputStream resource = getClass().getResourceAsStream("/basic.ipynb");
|
||||
Nbformat nbformat = new JupyterUtil().getNbformat(new InputStreamReader(resource));
|
||||
assertTrue(nbformat.getCells().get(0) instanceof CodeCell);
|
||||
|
||||
resource = getClass().getResourceAsStream("/examples.ipynb");
|
||||
nbformat = new JupyterUtil().getNbformat(new InputStreamReader(resource));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNote() {
|
||||
InputStream resource = getClass().getResourceAsStream("/examples.ipynb");
|
||||
Note n = new JupyterUtil().getNote(new InputStreamReader(resource), "%python", "%md");
|
||||
}
|
||||
|
||||
}
|
||||
149
zeppelin-jupyter/src/test/resources/basic.ipynb
Normal file
149
zeppelin-jupyter/src/test/resources/basic.ipynb
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The first two lines of the A matrix represent the coordinates of each rotor in the X,Y plane,\n",
|
||||
"# and the third line the direction in which they spin.\n",
|
||||
"# Note that in this example the X axis is vertical, and the Y coordinates are in the top row of A.\n",
|
||||
"A = np.array([[-0.17, 0.17, -0.25, 0.25, -0.33, 0.33],\n",
|
||||
" [-0.35, -0.35, 0., 0., 0.35, 0.35],\n",
|
||||
" [-0.1, 0.1, 0.1, -0.1, -0.1, 0.1 ]])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Moore-Penrose pseudoinverse of A\n",
|
||||
"B = np.linalg.pinv(A)\n",
|
||||
"# normalize roll/pitch to the largest of both\n",
|
||||
"# normalize yaw to 0.5\n",
|
||||
"# and transpose\n",
|
||||
"rp_max = B[:,0:1].max()\n",
|
||||
"n = np.array([rp_max, rp_max, 2*B[:,2].max()])\n",
|
||||
"B_nt = (B / n).T"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[[ -67 67 -256 256 -189 189]\n",
|
||||
" [-197 -197 0 0 197 197]\n",
|
||||
" [ -77 77 128 -128 -57 57]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# scale and round to 256 to return final coefficients\n",
|
||||
"scale = 256\n",
|
||||
"coeffs = np.around(scale * B_nt).astype(int)\n",
|
||||
"print(coeffs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"<define name=\"ROLL_COEF\" value=\"{ -67, 67, -256, 256, -189, 189}\"/>\n",
|
||||
"<define name=\"PITCH_COEF\" value=\"{-197, -197, 0, 0, 197, 197}\"/>\n",
|
||||
"<define name=\"YAW_COEF\" value=\"{ -77, 77, 128, -128, -57, 57}\"/>\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# output defines\n",
|
||||
"import string\n",
|
||||
"rows = ['ROLL_COEF\" ', 'PITCH_COEF\" ', 'YAW_COEF\" ']\n",
|
||||
"for i, r in enumerate(rows):\n",
|
||||
" print('<define name=\"' + r + 'value=\"{' + string.join(['{:>4d}'.format(c) for c in coeffs[i]], ', ') + '}\"/>')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 2",
|
||||
"language": "python",
|
||||
"name": "python2"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2.0
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
355
zeppelin-jupyter/src/test/resources/examples.ipynb
Normal file
355
zeppelin-jupyter/src/test/resources/examples.ipynb
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -297,6 +297,16 @@ public class ActiveDirectoryGroupRealm extends AbstractLdapRealm {
|
|||
return userNameList;
|
||||
}
|
||||
|
||||
public Map<String, String> getListRoles() {
|
||||
Map<String, String> roles = new HashMap<>();
|
||||
Iterator it = this.groupRolesMap.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry pair = (Map.Entry) it.next();
|
||||
roles.put((String) pair.getValue(), "*");
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
private Set<String> getRoleNamesForUser(String username, LdapContext ldapContext)
|
||||
throws NamingException {
|
||||
Set<String> roleNames = new LinkedHashSet<>();
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ import javax.naming.ldap.PagedResultsControl;
|
|||
* # ability set searchScopes subtree (default), one, base
|
||||
* ldapRealm.userSearchScope = subtree;
|
||||
* ldapRealm.groupSearchScope = subtree;
|
||||
* ldapRealm.userSearchFilter = (&(objectclass=person)(sAMAccountName={0}))
|
||||
* ldapRealm.groupSearchFilter = (&(objectclass=groupofnames)(member={0}))
|
||||
* ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,
|
||||
* dc=org
|
||||
* # enable support for nested groups using the LDAP_MATCHING_RULE_IN_CHAIN operator
|
||||
|
|
@ -160,6 +162,7 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
private Pattern principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
|
||||
private String userDnTemplate = "{0}";
|
||||
private String userSearchFilter = null;
|
||||
private String groupSearchFilter = null;
|
||||
private String userSearchAttributeTemplate = "{0}";
|
||||
private String userSearchScope = "subtree";
|
||||
private String groupSearchScope = "subtree";
|
||||
|
|
@ -356,9 +359,22 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Default group search filter
|
||||
String searchFilter = String.format("(objectclass=%1$s)", groupObjectClass);
|
||||
|
||||
// If group search filter is defined in Shiro config, then use it
|
||||
if (groupSearchFilter != null) {
|
||||
Matcher matchedPrincipal = matchPrincipal(userDn);
|
||||
searchFilter = expandTemplate(groupSearchFilter, matchedPrincipal);
|
||||
//searchFilter = String.format("%1$s", groupSearchFilter);
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Group SearchBase|SearchFilter|GroupSearchScope: " + getGroupSearchBase()
|
||||
+ "|" + searchFilter + "|" + groupSearchScope);
|
||||
}
|
||||
searchResultEnum = ldapCtx.search(
|
||||
getGroupSearchBase(),
|
||||
"objectClass=" + groupObjectClass,
|
||||
searchFilter,
|
||||
searchControls);
|
||||
while (searchResultEnum != null && searchResultEnum.hasMore()) {
|
||||
// searchResults contains all the groups in search scope
|
||||
|
|
@ -737,6 +753,14 @@ public class LdapRealm extends JndiLdapRealm {
|
|||
this.userSearchFilter = (filter == null ? null : filter.trim());
|
||||
}
|
||||
|
||||
public String getGroupSearchFilter() {
|
||||
return groupSearchFilter;
|
||||
}
|
||||
|
||||
public void setGroupSearchFilter(final String filter) {
|
||||
this.groupSearchFilter = (filter == null ? null : filter.trim());
|
||||
}
|
||||
|
||||
public boolean getUserLowerCase() {
|
||||
return userLowerCase;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import org.apache.zeppelin.annotation.ZeppelinApi;
|
|||
import org.apache.zeppelin.server.JsonResponse;
|
||||
import org.apache.zeppelin.util.Util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
|
|
@ -56,7 +59,12 @@ public class ZeppelinRestApi {
|
|||
@Path("version")
|
||||
@ZeppelinApi
|
||||
public Response getVersion() {
|
||||
return new JsonResponse<>(Response.Status.OK, "Zeppelin version", Util.getVersion()).build();
|
||||
Map<String, String> versionInfo = new HashMap<>();
|
||||
versionInfo.put("version", Util.getVersion());
|
||||
versionInfo.put("git-commit-id", Util.getGitCommitId());
|
||||
versionInfo.put("git-timestamp", Util.getGitTimestamp());
|
||||
|
||||
return new JsonResponse<>(Response.Status.OK, "Zeppelin version", versionInfo).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -226,7 +226,8 @@ public class NotebookServer extends WebSocketServlet
|
|||
addUserConnection(messagereceived.principal, conn);
|
||||
}
|
||||
AuthenticationInfo subject =
|
||||
new AuthenticationInfo(messagereceived.principal, messagereceived.ticket);
|
||||
new AuthenticationInfo(messagereceived.principal, messagereceived.roles,
|
||||
messagereceived.ticket);
|
||||
|
||||
/** Lets be elegant here */
|
||||
switch (messagereceived.op) {
|
||||
|
|
@ -1807,7 +1808,7 @@ public class NotebookServer extends WebSocketServlet
|
|||
p.setText(text);
|
||||
p.setTitle(title);
|
||||
AuthenticationInfo subject =
|
||||
new AuthenticationInfo(fromMessage.principal, fromMessage.ticket);
|
||||
new AuthenticationInfo(fromMessage.principal, fromMessage.roles, fromMessage.ticket);
|
||||
p.setAuthenticationInfo(subject);
|
||||
p.settings.setParams(params);
|
||||
p.setConfig(config);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import org.apache.shiro.subject.Subject;
|
|||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.zeppelin.conf.ZeppelinConfiguration;
|
||||
import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm;
|
||||
import org.apache.zeppelin.realm.LdapRealm;
|
||||
import org.mortbay.log.Log;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -133,6 +134,9 @@ public class SecurityUtils {
|
|||
} else if (name.equals("org.apache.zeppelin.realm.LdapRealm")) {
|
||||
allRoles = ((LdapRealm) realm).getListRoles();
|
||||
break;
|
||||
} else if (name.equals("org.apache.zeppelin.realm.ActiveDirectoryGroupRealm")) {
|
||||
allRoles = ((ActiveDirectoryGroupRealm) realm).getListRoles();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allRoles != null) {
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
}
|
||||
try {
|
||||
String xpathToRunOnSelectionChangeCheckbox = getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]";
|
||||
String xpathToDropdownMenu = "(" + (getParagraphXPath(1) + "//select)[2]");
|
||||
String xpathToDropdownMenu = getParagraphXPath(1) + "//select";
|
||||
String xpathToResultText = getParagraphXPath(1) + "//div[contains(@id,\"_html\")]";
|
||||
|
||||
createNewNote();
|
||||
|
|
@ -592,7 +592,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.equalTo("Howdy "));
|
||||
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
|
||||
dropDownMenu.selectByVisibleText("Alice");
|
||||
collector.checkThat("After selection in drop down menu, output should display the newly selected option",
|
||||
|
|
@ -602,7 +602,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
sameDropDownMenu.selectByVisibleText("Bob");
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if selecting a different option",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -632,7 +632,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.containsString("Greetings han and leia and luke"));
|
||||
|
||||
WebElement firstCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[2]"));
|
||||
WebElement firstCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[1]"));
|
||||
firstCheckbox.click();
|
||||
collector.checkThat("After unchecking one of the boxes, we can see the newly updated output without the option we unchecked",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -641,7 +641,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
WebElement secondCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[3]"));
|
||||
WebElement secondCheckbox = driver.findElement(By.xpath("(" + getParagraphXPath(1) + "//input[@type='checkbox'])[2]"));
|
||||
secondCheckbox.click();
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if check box state is modified",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -676,7 +676,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
CoreMatchers.equalTo("Howdy \nHowdy "));
|
||||
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
Select dropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[1]"))));
|
||||
dropDownMenu.selectByVisibleText("Apple");
|
||||
collector.checkThat("After selection in drop down menu, output should display the new option we selected",
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@class, 'text plainTextContent')]")).getText(),
|
||||
|
|
@ -685,7 +685,7 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
driver.findElement(By.xpath(getParagraphXPath(1) + "//span[@class='icon-settings']")).click();
|
||||
clickAndWait(By.xpath(getParagraphXPath(1) + "//ul/li/form/input[contains(@ng-checked, 'true')]"));
|
||||
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[3]"))));
|
||||
Select sameDropDownMenu = new Select(driver.findElement(By.xpath("(" + (getParagraphXPath(1) + "//select)[2]"))));
|
||||
sameDropDownMenu.selectByVisibleText("Earth");
|
||||
waitForParagraph(1, "FINISHED");
|
||||
collector.checkThat("After 'Run on selection change' checkbox is unchecked, the paragraph should not run if selecting a different option",
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
|
|||
}
|
||||
|
||||
collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ",
|
||||
headerNames, CoreMatchers.equalTo("Age|Job|Marital|Education|Balance|"));
|
||||
headerNames, CoreMatchers.equalTo("age|job|marital|education|balance|"));
|
||||
} catch (Exception e) {
|
||||
handleException("Exception in SparkParagraphIT while testSqlSpark", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
|
|||
assertEquals("paragraph col width check failed", 9.0, p.getConfig().get("colWidth"));
|
||||
assertTrue("paragraph show title check failed", ((boolean) p.getConfig().get("title")));
|
||||
Map graph = ((List<Map>)p.getConfig().get("results")).get(0);
|
||||
String mode = graph.get("mode").toString();
|
||||
String mode = ((Map)graph.get("graph")).get("mode").toString();
|
||||
assertEquals("paragraph graph mode check failed", "pieChart", mode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"no-undef": 2,
|
||||
"no-unused-vars": [2, { "vars": "local", "args": "none" }],
|
||||
"strict": [2, "global"],
|
||||
"max-len": [2, {"code": 120, "ignoreComments": true, "ignoreRegExpLiterals": true}]
|
||||
"max-len": [2, {"code": 120, "ignoreComments": true, "ignoreRegExpLiterals": true}],
|
||||
"linebreak-style": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ let zeppelinWebApp = angular.module('zeppelinWebApp', requiredModules)
|
|||
})
|
||||
.when('/jobmanager', {
|
||||
templateUrl: 'app/jobmanager/jobmanager.html',
|
||||
controller: 'JobmanagerCtrl'
|
||||
controller: 'JobManagerCtrl'
|
||||
})
|
||||
.when('/interpreter', {
|
||||
templateUrl: 'app/interpreter/interpreter.html',
|
||||
|
|
|
|||
|
|
@ -252,13 +252,13 @@ limitations under the License.
|
|||
<div ng-show="newInterpreterSetting.option.setPermission" class="permissionsForm">
|
||||
<div>
|
||||
<p>
|
||||
Enter comma separated users in the fields. <br />
|
||||
Enter comma separated users and groups 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 id="newInterpreterOwners" class="form-control" multiple="multiple">
|
||||
<option ng-repeat="owner in newInterpreterSetting.option.owners" selected="selected">{{owner}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
|
||||
let getSelectJson = function () {
|
||||
let selectJson = {
|
||||
tags: false,
|
||||
tags: true,
|
||||
minimumInputLength: 3,
|
||||
multiple: true,
|
||||
tokenSeparators: [',', ' '],
|
||||
minimumInputLength: 2,
|
||||
ajax: {
|
||||
url: function (params) {
|
||||
if (!params.term) {
|
||||
|
|
@ -52,17 +52,36 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
},
|
||||
delay: 250,
|
||||
processResults: function (data, params) {
|
||||
let users = []
|
||||
let results = []
|
||||
|
||||
if (data.body.users.length !== 0) {
|
||||
for (let i = 0; i < data.body.users.length; i++) {
|
||||
let users = []
|
||||
for (let len = 0; len < data.body.users.length; len++) {
|
||||
users.push({
|
||||
'id': data.body.users[i],
|
||||
'text': data.body.users[i]
|
||||
'id': data.body.users[len],
|
||||
'text': data.body.users[len]
|
||||
})
|
||||
}
|
||||
results.push({
|
||||
'text': 'Users :',
|
||||
'children': users
|
||||
})
|
||||
}
|
||||
if (data.body.roles.length !== 0) {
|
||||
let roles = []
|
||||
for (let len = 0; len < data.body.roles.length; len++) {
|
||||
roles.push({
|
||||
'id': data.body.roles[len],
|
||||
'text': data.body.roles[len]
|
||||
})
|
||||
}
|
||||
results.push({
|
||||
'text': 'Roles :',
|
||||
'children': roles
|
||||
})
|
||||
}
|
||||
return {
|
||||
results: users,
|
||||
results: results,
|
||||
pagination: {
|
||||
more: false
|
||||
}
|
||||
|
|
@ -75,7 +94,7 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
}
|
||||
|
||||
$scope.togglePermissions = function (intpName) {
|
||||
angular.element('#' + intpName + 'Users').select2(getSelectJson())
|
||||
angular.element('#' + intpName + 'Owners').select2(getSelectJson())
|
||||
if ($scope.showInterpreterAuth) {
|
||||
$scope.closePermissions()
|
||||
} else {
|
||||
|
|
@ -85,7 +104,7 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
|
||||
$scope.$on('ngRenderFinished', function (event, data) {
|
||||
for (let setting = 0; setting < $scope.interpreterSettings.length; setting++) {
|
||||
angular.element('#' + $scope.interpreterSettings[setting].name + 'Users').select2(getSelectJson())
|
||||
angular.element('#' + $scope.interpreterSettings[setting].name + 'Owners').select2(getSelectJson())
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -359,7 +378,7 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
// remote always true for now
|
||||
setting.option.remote = true
|
||||
}
|
||||
setting.option.users = angular.element('#' + setting.name + 'Users').val()
|
||||
setting.option.owners = angular.element('#' + setting.name + 'Owners').val()
|
||||
|
||||
let request = {
|
||||
option: angular.copy(setting.option),
|
||||
|
|
@ -498,7 +517,7 @@ function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeou
|
|||
if (newSetting.option.setPermission === undefined) {
|
||||
newSetting.option.setPermission = false
|
||||
}
|
||||
newSetting.option.users = angular.element('#newInterpreterUsers').val()
|
||||
newSetting.option.owners = angular.element('#newInterpreterOwners').val()
|
||||
|
||||
let request = angular.copy($scope.newInterpreterSetting)
|
||||
|
||||
|
|
|
|||
|
|
@ -372,14 +372,14 @@ limitations under the License.
|
|||
<div ng-show="setting.option.setPermission" class="permissionsForm">
|
||||
<div>
|
||||
<p>
|
||||
Enter comma separated users in the fields. <br />
|
||||
Enter comma separated users and groups in the fields. <br />
|
||||
Empty field (*) implies anyone can run this interpreter.
|
||||
</p>
|
||||
<div>
|
||||
|
||||
<span class="owners">Owners </span>
|
||||
<select id="{{setting.name}}Users" class="form-control" multiple="multiple" ng-disabled="!valueform.$visible">
|
||||
<option ng-repeat="user in setting.option.users" selected="selected">{{user}}</option>
|
||||
<select id="{{setting.name}}Owners" class="form-control" multiple="multiple" ng-disabled="!valueform.$visible">
|
||||
<option ng-repeat="owner in setting.option.owners" selected="selected">{{owner}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,40 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
angular.module('zeppelinWebApp')
|
||||
.controller('JobmanagerCtrl', JobmanagerCtrl)
|
||||
import { JobStatus, } from './jobs/job-status'
|
||||
|
||||
function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) {
|
||||
angular.module('zeppelinWebApp')
|
||||
.controller('JobManagerCtrl', JobManagerCtrl)
|
||||
|
||||
const JobDateSorter = {
|
||||
RECENTLY_UPDATED: 'Recently Update',
|
||||
OLDEST_UPDATED: 'Oldest Updated',
|
||||
}
|
||||
|
||||
function JobManagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) {
|
||||
'ngInject'
|
||||
|
||||
$scope.pagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 10,
|
||||
maxPageCount: 5,
|
||||
}
|
||||
|
||||
$scope.sorter = {
|
||||
AvailableDateSorter: Object.keys(JobDateSorter).map(key => { return JobDateSorter[key] }),
|
||||
currentDateSorter: JobDateSorter.RECENTLY_UPDATED,
|
||||
}
|
||||
|
||||
$scope.setJobDateSorter = function(dateSorter) {
|
||||
$scope.sorter.currentDateSorter = dateSorter
|
||||
}
|
||||
|
||||
$scope.getJobsInCurrentPage = function(jobs) {
|
||||
const cp = $scope.pagination.currentPage
|
||||
const itp = $scope.pagination.itemsPerPage
|
||||
return jobs.slice((cp - 1) * itp, (cp * itp))
|
||||
}
|
||||
|
||||
ngToast.dismiss()
|
||||
let asyncNotebookJobFilter = function (jobInfomations, filterConfig) {
|
||||
return $q(function (resolve, reject) {
|
||||
|
|
@ -26,15 +54,52 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo
|
|||
})
|
||||
}
|
||||
|
||||
$scope.$watch('sorter.currentDateSorter', function() {
|
||||
$scope.filterConfig.isSortByAsc =
|
||||
$scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED
|
||||
asyncNotebookJobFilter($scope.jobInfomations, $scope.filterConfig)
|
||||
})
|
||||
|
||||
$scope.getJobIconByStatus = function(jobStatus) {
|
||||
if (jobStatus === JobStatus.READY) {
|
||||
return 'fa fa-circle-o'
|
||||
} else if (jobStatus === JobStatus.FINISHED) {
|
||||
return 'fa fa-circle'
|
||||
} else if (jobStatus === JobStatus.ABORT) {
|
||||
return 'fa fa-circle'
|
||||
} else if (jobStatus === JobStatus.ERROR) {
|
||||
return 'fa fa-circle'
|
||||
} else if (jobStatus === JobStatus.PENDING) {
|
||||
return 'fa fa-circle'
|
||||
} else if (jobStatus === JobStatus.RUNNING) {
|
||||
return 'fa fa-spinner'
|
||||
}
|
||||
}
|
||||
|
||||
$scope.getJobColorByStatus = function(jobStatus) {
|
||||
if (jobStatus === JobStatus.READY) {
|
||||
return 'green'
|
||||
} else if (jobStatus === JobStatus.FINISHED) {
|
||||
return 'green'
|
||||
} else if (jobStatus === JobStatus.ABORT) {
|
||||
return 'orange'
|
||||
} else if (jobStatus === JobStatus.ERROR) {
|
||||
return 'red'
|
||||
} else if (jobStatus === JobStatus.PENDING) {
|
||||
return 'gray'
|
||||
} else if (jobStatus === JobStatus.RUNNING) {
|
||||
return 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
$scope.doFiltering = function (jobInfomations, filterConfig) {
|
||||
asyncNotebookJobFilter(jobInfomations, filterConfig).then(
|
||||
function () {
|
||||
// success
|
||||
$scope.isLoadingFilter = false
|
||||
},
|
||||
function () {
|
||||
// failed
|
||||
})
|
||||
asyncNotebookJobFilter(jobInfomations, filterConfig)
|
||||
.then(
|
||||
() => { $scope.isLoadingFilter = false },
|
||||
(error) => {
|
||||
console.error('Failed to search jobs from server', error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$scope.filterValueToName = function (filterValue, maxStringLength) {
|
||||
|
|
@ -48,7 +113,7 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo
|
|||
}
|
||||
return $scope.activeInterpreters[index].name
|
||||
} else {
|
||||
return 'Interpreter is not set'
|
||||
return 'NONE'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,37 +122,6 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo
|
|||
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
|
||||
}
|
||||
|
||||
$scope.onChangeRunJobToAlwaysTopToggle = function () {
|
||||
$scope.filterConfig.isRunningAlwaysTop = !$scope.filterConfig.isRunningAlwaysTop
|
||||
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
|
||||
}
|
||||
|
||||
$scope.onChangeSortAsc = function () {
|
||||
$scope.filterConfig.isSortByAsc = !$scope.filterConfig.isSortByAsc
|
||||
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
|
||||
}
|
||||
|
||||
$scope.doFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) {
|
||||
let RETURN_KEY_CODE = 13
|
||||
$timeout.cancel($scope.dofilterTimeoutObject)
|
||||
$scope.isActiveSearchTimer = true
|
||||
$scope.dofilterTimeoutObject = $timeout(function () {
|
||||
$scope.doFiltering(jobInfomations, filterConfig)
|
||||
$scope.isActiveSearchTimer = false
|
||||
}, 10000)
|
||||
if (keyEvent.which === RETURN_KEY_CODE) {
|
||||
$timeout.cancel($scope.dofilterTimeoutObject)
|
||||
$scope.doFiltering(jobInfomations, filterConfig)
|
||||
$scope.isActiveSearchTimer = false
|
||||
}
|
||||
}
|
||||
|
||||
$scope.doForceFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) {
|
||||
$timeout.cancel($scope.dofilterTimeoutObject)
|
||||
$scope.doFiltering(jobInfomations, filterConfig)
|
||||
$scope.isActiveSearchTimer = false
|
||||
}
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.isLoadingFilter = true
|
||||
$scope.jobInfomations = []
|
||||
|
|
@ -96,21 +130,13 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo
|
|||
isRunningAlwaysTop: true,
|
||||
filterValueNotebookName: '',
|
||||
filterValueInterpreter: '*',
|
||||
isSortByAsc: true
|
||||
isSortByAsc: $scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED,
|
||||
}
|
||||
$scope.sortTooltipMsg = 'Switch to sort by desc'
|
||||
$scope.jobTypeFilter = jobManagerFilter
|
||||
|
||||
websocketMsgSrv.getNoteJobsList()
|
||||
|
||||
$scope.$watch('filterConfig.isSortByAsc', function (value) {
|
||||
if (value) {
|
||||
$scope.sortTooltipMsg = 'Switch to sort by desc'
|
||||
} else {
|
||||
$scope.sortTooltipMsg = 'Switch to sort by asc'
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
websocketMsgSrv.unsubscribeJobManager()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
min-height: 32px;
|
||||
}
|
||||
|
||||
.jobManagerHead {
|
||||
.job-manager-header {
|
||||
margin: -10px -10px 20px;
|
||||
padding: 10px 15px 15px 15px;
|
||||
background: white;
|
||||
|
|
@ -30,18 +30,88 @@
|
|||
border-bottom: 1px solid #E5E5E5;
|
||||
}
|
||||
|
||||
.jobManagerHead .header {
|
||||
.job-manager-header .header {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.job-note-name-query {
|
||||
padding: 6px;
|
||||
height: 25px;
|
||||
width: 200px;
|
||||
.job-search-tool {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.job-note-name-font-family {
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
#job-manager-header .job-search-tool .search-input {
|
||||
margin-right: 7px;
|
||||
min-width: 215px;
|
||||
}
|
||||
|
||||
#job-manager-header .job-search-tool .search-input > input {
|
||||
font-family: 'FontAwesome', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#job-manager-header .job-search-tool .dropdown-toggle {
|
||||
border-radius: 3px;
|
||||
float: none;
|
||||
min-width: 150px;
|
||||
text-align: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
||||
#job-manager-header .job-search-tool .date-sort-button {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.job-search-tool .dropdown-text-desc {
|
||||
color: gray;
|
||||
font-weight: 400;
|
||||
font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.job-search-tool .dropdown-text-value {
|
||||
margin-left: 2px;
|
||||
font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.search-tool-dropdown-content > li > a {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.search-tool-dropdown-content > li:last-child > a {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.job-icon-desc-container {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
margin-right: 30px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.job-desc-icon {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.job-pagination-container {
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.job-counter {
|
||||
float: right;
|
||||
clear: both;
|
||||
margin-left: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.job-counter .job-counter-label {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.job-counter .job-counter-value {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,26 +22,34 @@ function jobManagerFilter () {
|
|||
let filterItems = jobItems
|
||||
|
||||
if (filterValueInterpreter === undefined) {
|
||||
filterItems = _.filter(filterItems, function (jobItem) {
|
||||
return jobItem.interpreter === undefined ? true : false
|
||||
filterItems = filterItems.filter((jobItem) => {
|
||||
return jobItem.interpreter === undefined
|
||||
})
|
||||
} else if (filterValueInterpreter !== '*') {
|
||||
filterItems = _.where(filterItems, {interpreter: filterValueInterpreter})
|
||||
}
|
||||
|
||||
if (filterValueNotebookName !== '') {
|
||||
filterItems = _.filter(filterItems, function (jobItem) {
|
||||
filterItems = filterItems.filter((jobItem) => {
|
||||
let lowerFilterValue = filterValueNotebookName.toLocaleLowerCase()
|
||||
let lowerNotebookName = jobItem.noteName.toLocaleLowerCase()
|
||||
return lowerNotebookName.match(new RegExp('.*' + lowerFilterValue + '.*'))
|
||||
})
|
||||
}
|
||||
|
||||
filterItems = _.sortBy(filterItems, function (sortItem) {
|
||||
return sortItem.noteName.toLowerCase()
|
||||
filterItems = filterItems.sort((jobItem) => {
|
||||
return jobItem.noteName.toLowerCase()
|
||||
})
|
||||
|
||||
return isSortByAsc ? filterItems : filterItems.reverse()
|
||||
filterItems = filterItems.sort((x, y) => {
|
||||
if (isSortByAsc) {
|
||||
return x.unixTimeLastRun - y.unixTimeLastRun
|
||||
} else {
|
||||
return y.unixTimeLastRun - x.unixTimeLastRun
|
||||
}
|
||||
})
|
||||
|
||||
return filterItems
|
||||
}
|
||||
return filterContext
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ 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.
|
||||
-->
|
||||
<!-- Here the controller <JobmanagerCtrl> is not needed because explicitly set in the app.js (route) -->
|
||||
<div class="jobManagerHead" data-ng-init="init()">
|
||||
<!-- Here the controller <JobManagerCtrl> is not needed because explicitly set in the app.js (route) -->
|
||||
<div id="job-manager-header" class="job-manager-header" data-ng-init="init()">
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
|
@ -27,138 +27,117 @@ limitations under the License.
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0px">
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;" />
|
||||
</div>
|
||||
|
||||
<hr style="margin-top: 15px; margin-bottom: 15px;" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-left">
|
||||
<!-- search tools (input, drop-down, sorting) -->
|
||||
<div class="job-search-tool">
|
||||
<div class="form-inline">
|
||||
<span class="labelBtn btn-group">
|
||||
<button type="button"
|
||||
class="btn btn-default"
|
||||
style="width: 25px; height: 25px; margin-right: 0px; padding: 1px 0px 3px 3px"
|
||||
ng-click="onChangeSortAsc()"
|
||||
tooltip-placement="right" uib-tooltip="{{sortTooltipMsg}}">
|
||||
<i class="fa" ng-class="{true: 'fa-sort-amount-asc', false : 'fa-sort-amount-desc'}[filterConfig.isSortByAsc]"></i>
|
||||
</button>
|
||||
</span>
|
||||
<span class="labelBtn btn-group" style="margin-left: 0px; padding-left: 3px;">
|
||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
style="min-width: 100px; text-align: right !important;">
|
||||
<span class="text-right job-note-name-font-family">
|
||||
{{filterValueToName(filterConfig.filterValueInterpreter)}}<span class="caret" style="margin-left: 10px"></span>
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-left" role="menu">
|
||||
<span class="btn-group">
|
||||
<!-- search tool: input -->
|
||||
<div class="input-group search-input">
|
||||
<input class="form-control btn-xs"
|
||||
placeholder=" Search jobs..."
|
||||
type="text"
|
||||
ng-model="filterConfig.filterValueNotebookName"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }"
|
||||
ng-change="doFiltering(jobInfomations, filterConfig)" />
|
||||
</div>
|
||||
|
||||
<!-- search tool: default interpreter dropdown -->
|
||||
<div class="btn btn-default dropdown-toggle"
|
||||
data-toggle="dropdown">
|
||||
<span>
|
||||
<span class="dropdown-text-desc">Interpreter: </span>
|
||||
<span class="dropdown-text-value">{{filterValueToName(filterConfig.filterValueInterpreter)}}</span>
|
||||
<span class="caret" style="margin-top: 8px; float: right;"></span>
|
||||
<span style="clear: both;"></span>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-right search-tool-dropdown-content" role="menu">
|
||||
<li ng-repeat="interpreterOption in activeInterpreters">
|
||||
<a class="job-note-name-font-family" style="cursor:pointer"
|
||||
ng-click="setFilterValue(interpreterOption.value)">
|
||||
<a ng-click="setFilterValue(interpreterOption.value)"
|
||||
ng-style="(filterValueToName(interpreterOption.value) === 'ALL' || filterValueToName(interpreterOption.value) === 'NONE') ? { 'font-weight': 500 } : {}"
|
||||
class="dropdown-list-value">
|
||||
{{filterValueToName(interpreterOption.value)}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="job-note-name-query job-note-name-font-family form-control btn-xs"
|
||||
style="margin-left: 5px;"
|
||||
placeholder="Search for Job"
|
||||
type="text" ng-model="filterConfig.filterValueNotebookName"
|
||||
ng-keyup="doFilterInputTyping($event, jobInfomations, filterConfig, isLoadingFilter)"/>
|
||||
<span
|
||||
class="input-group-addon text-right" ng-class="{true : 'btn-primary active', false: ''}[isActiveSearchTimer]"
|
||||
style="height: 5px; padding: 0px 6px 0px 9px !important;"
|
||||
ng-click="doForceFilterInputTyping($event, jobInfomations, filterConfig, isLoadingFilter)">
|
||||
<i class="fa fa-search fa-sm"></i></span>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
|
||||
<span class="btn-group">
|
||||
<!-- search tool: date dropdown -->
|
||||
<div class="date-sort-button btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>
|
||||
<span class="dropdown-text-desc">Sort: </span>
|
||||
<span class="dropdown-text-value">{{sorter.currentDateSorter}}</span>
|
||||
<span class="caret" style="margin-top: 8px; float: right;"></span>
|
||||
<span style="clear: both;"></span>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-right search-tool-dropdown-content" role="menu">
|
||||
<li ng-repeat="dateSorter in sorter.AvailableDateSorter">
|
||||
<a ng-click="setJobDateSorter(dateSorter)" class="dropdown-list-value">
|
||||
{{dateSorter}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span class="job-counter">
|
||||
<span class="job-counter-label">Total: </span>
|
||||
<span class="job-counter-value">{{JobInfomationsByFilter.length}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-6 text-right"
|
||||
style="padding-top: 6px;">
|
||||
<span
|
||||
ng-repeat="jobStatus in ['READY', 'FINISHED', 'ABORT', 'ERROR','PENDING','RUNNING']"
|
||||
ng-switch="jobStatus">
|
||||
<span
|
||||
ng-switch-when="FINISHED">
|
||||
<i style="color: green; margin-right: 3px;" class="fa fa-circle"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
<span
|
||||
ng-switch-when="RUNNING"
|
||||
style="margin-right: 3px;">
|
||||
<i style="color: blue" class="fa fa-spinner"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
<span
|
||||
ng-switch-when="READY"
|
||||
style="margin-right: 3px;">
|
||||
<i style="color: green" class="fa fa-circle-o"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
<span
|
||||
ng-switch-when="PENDING"
|
||||
style="margin-right: 3px;">
|
||||
<i style="color: gray" class="fa fa-circle"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
<span
|
||||
ng-switch-when="ABORT"
|
||||
style="margin-right: 3px;">
|
||||
<i style="color: orange" class="fa fa-circle"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
<span
|
||||
ng-switch-when="ERROR"
|
||||
style="margin-right: 3px;">
|
||||
<i style="color: red" class="fa fa-circle"
|
||||
ng-click="">
|
||||
</i>
|
||||
{{jobStatus}}
|
||||
|
||||
<!-- job icon descriptions -->
|
||||
<div class="job-icon-desc-container hidden-xs hidden-sm hidden-md">
|
||||
<span ng-repeat="jobStatus in ['READY', 'FINISHED', 'ABORT', 'ERROR','PENDING','RUNNING']">
|
||||
<span style="margin-right: 2px;">
|
||||
<i class="job-desc-icon"
|
||||
ng-style="{'color': getJobColorByStatus(jobStatus)}"
|
||||
ng-class="getJobIconByStatus(jobStatus)" ></i>{{jobStatus}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="note-jump"></div>
|
||||
<div
|
||||
ng-if="isLoadingFilter === true"
|
||||
class="paragraph-col">
|
||||
<div
|
||||
class="job-space box job-margin text-center">
|
||||
<i style="color: blue" class="fa fa-spinner spinAnimation">
|
||||
</i> Loading...
|
||||
<div ng-if="isLoadingFilter === true" class="paragraph-col">
|
||||
<div class="job-space box job-margin text-center">
|
||||
<i style="color: blue" class="fa fa-spinner spinAnimation"></i>Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ng-if="JobInfomationsByFilter.length > 0"
|
||||
ng-repeat="notebookJob in JobInfomationsByFilter track by $index"
|
||||
class="paragraph-col">
|
||||
<div ng-if="JobInfomationsByFilter.length > 0"
|
||||
ng-repeat="notebookJob in getJobsInCurrentPage(JobInfomationsByFilter)"
|
||||
class="paragraph-col">
|
||||
<div ng-include src="'app/jobmanager/jobs/job.html'"
|
||||
class="job-space box job-margin"
|
||||
ng-controller="JobCtrl">
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ng-if="isLoadingFilter === false && JobInfomationsByFilter.length <= 0"
|
||||
class="paragraph-col">
|
||||
<div
|
||||
class="job-space box job-margin text-center">
|
||||
No Job found
|
||||
</div>
|
||||
<div ng-if="isLoadingFilter === false && JobInfomationsByFilter.length <= 0"
|
||||
class="paragraph-col">
|
||||
<div class="job-space box job-margin text-center">No Job found</div>
|
||||
</div>
|
||||
<div style="clear:both;height:10px"></div>
|
||||
|
||||
<!-- pagination -->
|
||||
<div class="job-pagination-container">
|
||||
<ul uib-pagination class="pagination-sm"
|
||||
total-items="JobInfomationsByFilter.length"
|
||||
ng-model="pagination.currentPage"
|
||||
items-per-page="pagination.itemsPerPage"
|
||||
boundary-links="true" rotate="false"
|
||||
max-size="pagination.maxPageCount"
|
||||
previous-text="‹" next-text="›"
|
||||
first-text="«" last-text="»"></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
22
zeppelin-web/src/app/jobmanager/jobs/job-status.js
Normal file
22
zeppelin-web/src/app/jobmanager/jobs/job-status.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const JobStatus = {
|
||||
READY: 'READY',
|
||||
FINISHED: 'FINISHED',
|
||||
ABORT: 'ABORT',
|
||||
ERROR: 'ERROR',
|
||||
PENDING: 'PENDING',
|
||||
RUNNING: 'RUNNING',
|
||||
}
|
||||
|
|
@ -15,89 +15,28 @@ limitations under the License.
|
|||
<div class="job" data-ng-init="init(notebookJob)">
|
||||
<div>
|
||||
<div ng-include src="'app/jobmanager/jobs/job-control.html'"></div>
|
||||
<span
|
||||
class="job-types"
|
||||
ng-switch="notebookJob.noteType">
|
||||
<span class="job-types"
|
||||
ng-switch="notebookJob.noteType">
|
||||
<i ng-switch-when="normal" class="icon-doc"></i>
|
||||
<i ng-switch-when="cron" class="icon-clock"></i>
|
||||
<i ng-switch-default class="icon-question"></i>
|
||||
</span>
|
||||
|
||||
<a style="text-decoration: none !important;" ng-href="#/notebook/{{notebookJob.noteId}}">
|
||||
<span>
|
||||
{{notebookJob.noteName}}
|
||||
</span>
|
||||
<span>
|
||||
-
|
||||
</span>
|
||||
<span>
|
||||
<span ng-if="notebookJob.interpreter === undefined" style="color: orange">
|
||||
Interpreter is not set
|
||||
</span>
|
||||
<span ng-if="notebookJob.interpreter !== undefined" style="color: gray">
|
||||
{{notebookJob.interpreter}}
|
||||
</span>
|
||||
</span>
|
||||
<span>{{notebookJob.noteName}} - </span>
|
||||
<span ng-if="notebookJob.interpreter === undefined" style="color: gray;">
|
||||
interpreter is not set</span>
|
||||
<span ng-if="notebookJob.interpreter !== undefined" style="color: black;">
|
||||
{{notebookJob.interpreter}}</span>
|
||||
</a>
|
||||
<div ng-include src="'app/jobmanager/jobs/job-progressBar.html'"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span
|
||||
ng-repeat="paragraphJob in notebookJob.paragraphs"
|
||||
ng-switch="paragraphJob.status">
|
||||
<a ng-switch-when="READY"
|
||||
style="text-decoration: none !important;"
|
||||
<span ng-repeat="paragraphJob in notebookJob.paragraphs">
|
||||
<a style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: green" class="fa fa-circle-o"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is READY">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-when="FINISHED"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: green" class="fa fa-circle"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is FINISHED">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-when="ABORT"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: orange" class="fa fa-circle"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is ABORT">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-when="ERROR"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: red" class="fa fa-circle"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is ERROR">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-when="PENDING"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: gray" class="fa fa-circle"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is PENDING">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-when="RUNNING"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i style="color: blue" class="fa fa-spinner spinAnimation"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is RUNNING">
|
||||
</i>
|
||||
</a>
|
||||
<a ng-switch-default class="icon-question"
|
||||
style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<i class="icon-question"
|
||||
<i ng-style="{'color': $parent.getJobColorByStatus(paragraphJob.status)}"
|
||||
ng-class="$parent.getJobIconByStatus(paragraphJob.status)"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraphJob.name}} is {{paragraphJob.status}}">
|
||||
</i>
|
||||
|
|
|
|||
|
|
@ -174,12 +174,19 @@
|
|||
}
|
||||
|
||||
.paragraph .control {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
float: right;
|
||||
background: rgba(255,255,255,0.85);
|
||||
color: #999;
|
||||
margin-top: 1px;
|
||||
margin-right: 5px;
|
||||
position: absolute;
|
||||
clear: both;
|
||||
right: 15px;
|
||||
top: 16px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.paragraph .control li {
|
||||
|
|
@ -268,11 +275,8 @@ table.table-shortcut {
|
|||
*/
|
||||
|
||||
.paragraph .title {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
margin-bottom: 7px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.paragraph .title div {
|
||||
|
|
@ -560,3 +564,7 @@ table.table-striped {
|
|||
.markdown-body h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.network-labels {
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,29 +15,24 @@ limitations under the License.
|
|||
<div id="{{paragraph.id}}_container"
|
||||
ng-class="{'paragraph': !asIframe, 'paragraphAsIframe': asIframe}">
|
||||
|
||||
<div>
|
||||
<div ng-if="paragraph.config.title"
|
||||
id="{{paragraph.id}}_title"
|
||||
ng-controller="ElasticInputCtrl as input"
|
||||
class="title">
|
||||
<input type="text"
|
||||
pu-elastic-input
|
||||
style="min-width: 400px; max-width: 80%;"
|
||||
placeholder="Untitled"
|
||||
ng-model="paragraph.title"
|
||||
ng-if="input.showEditor"
|
||||
ng-escape="input.showEditor = false; paragraph.title = oldTitle;"
|
||||
ng-blur="setTitle(paragraph); input.showEditor = false"
|
||||
ng-enter="setTitle(paragraph); input.showEditor = false"
|
||||
focus-if="input.showEditor" />
|
||||
<div ng-click="input.showEditor = !asIframe && !viewOnly && !revisionView; oldTitle = paragraph.title;"
|
||||
ng-show="!input.showEditor"
|
||||
ng-bind-html="paragraph.title || 'Untitled'">
|
||||
</div>
|
||||
<div ng-if="paragraph.config.title"
|
||||
id="{{paragraph.id}}_title"
|
||||
ng-controller="ElasticInputCtrl as input"
|
||||
class="title">
|
||||
<input type="text"
|
||||
pu-elastic-input
|
||||
style="min-width: 400px; max-width: 80%;"
|
||||
placeholder="Untitled"
|
||||
ng-model="paragraph.title"
|
||||
ng-if="input.showEditor"
|
||||
ng-escape="input.showEditor = false; paragraph.title = oldTitle;"
|
||||
ng-blur="setTitle(paragraph); input.showEditor = false"
|
||||
ng-enter="setTitle(paragraph); input.showEditor = false"
|
||||
focus-if="input.showEditor" />
|
||||
<div ng-click="input.showEditor = !asIframe && !viewOnly && !revisionView; oldTitle = paragraph.title;"
|
||||
ng-show="!input.showEditor"
|
||||
ng-bind-html="paragraph.title || 'Untitled'">
|
||||
</div>
|
||||
|
||||
<div ng-include src="'app/notebook/paragraph/paragraph-control.html'"></div>
|
||||
<div style="display: inline-block; clear: both;"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -68,6 +63,8 @@ limitations under the License.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include src="'app/notebook/paragraph/paragraph-control.html'"></div>
|
||||
|
||||
<div ng-if="!asIframe" class="paragraphFooter">
|
||||
<div ng-show="!paragraph.config.tableHide && !viewOnly"
|
||||
id="{{paragraph.id}}_executionTime"
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ limitations under the License.
|
|||
-->
|
||||
|
||||
<div id="{{id}}_switch"
|
||||
ng-if="(type == 'TABLE' || apps.length > 0 || suggestion.available && suggestion.available.length > 0) && !asIframe && !viewOnly"
|
||||
ng-if="(type == 'TABLE' || type == 'NETWORK' || apps.length > 0 || suggestion.available && suggestion.available.length > 0) && !asIframe && !viewOnly"
|
||||
class="result-chart-selector">
|
||||
|
||||
<div ng-if="type == 'TABLE'" class="btn-group">
|
||||
<div ng-if="type == 'TABLE' || type == 'NETWORK'" class="btn-group">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-repeat="viz in builtInTableDataVisualizationList track by $index"
|
||||
ng-if="viz.supports.indexOf(type) > -1"
|
||||
ng-class="{'active' : viz.id == graphMode && !config.helium.activeApp}"
|
||||
ng-click="switchViz(viz.id)"
|
||||
tooltip-placement="bottom" uib-tooltip="{{viz.name ? viz.name : ''}}"
|
||||
|
|
@ -28,7 +29,7 @@ limitations under the License.
|
|||
|
||||
<div class="btn-group">
|
||||
<button type="button"
|
||||
ng-if="type != 'TABLE'"
|
||||
ng-if="type != 'TABLE' && type != 'NETWORK'"
|
||||
ng-click="switchApp()"
|
||||
ng-class="{'active' : !config.helium.activeApp}"
|
||||
class="btn btn-default btn-sm"><i class="fa fa-terminal"></i>
|
||||
|
|
@ -73,7 +74,7 @@ limitations under the License.
|
|||
</div>
|
||||
|
||||
<div class="btn-group"
|
||||
ng-if="type == 'TABLE' && !asIframe && !viewOnly"
|
||||
ng-if="(type == 'TABLE' || type == 'NETWORK') && !asIframe && !viewOnly"
|
||||
style="margin-bottom: 10px;">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
style="margin-left:10px"
|
||||
|
|
@ -93,7 +94,7 @@ limitations under the License.
|
|||
</div>
|
||||
|
||||
<span
|
||||
ng-if="type=='TABLE' && !config.helium.activeApp && !asIframe && !viewOnly"
|
||||
ng-if="(type == 'TABLE' || type == 'NETWORK') && !config.helium.activeApp && graphMode!='table' && !asIframe && !viewOnly"
|
||||
style="margin-left:10px; cursor:pointer; display: inline-block; vertical-align:top; position: relative; line-height:30px;">
|
||||
<a class="btnText" ng-click="toggleGraphSetting()">
|
||||
settings <span ng-class="config.graph.optionOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'"></span>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@
|
|||
|
||||
import moment from 'moment'
|
||||
|
||||
import TableData from '../../../tabledata/tabledata'
|
||||
import DatasetFactory from '../../../tabledata/datasetfactory'
|
||||
import TableVisualization from '../../../visualization/builtins/visualization-table'
|
||||
import BarchartVisualization from '../../../visualization/builtins/visualization-barchart'
|
||||
import PiechartVisualization from '../../../visualization/builtins/visualization-piechart'
|
||||
import AreachartVisualization from '../../../visualization/builtins/visualization-areachart'
|
||||
import LinechartVisualization from '../../../visualization/builtins/visualization-linechart'
|
||||
import ScatterchartVisualization from '../../../visualization/builtins/visualization-scatterchart'
|
||||
import NetworkVisualization from '../../../visualization/builtins/visualization-d3network'
|
||||
import {
|
||||
DefaultDisplayType,
|
||||
SpellResult,
|
||||
|
|
@ -44,36 +45,48 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
{
|
||||
id: 'table', // paragraph.config.graph.mode
|
||||
name: 'Table', // human readable name. tooltip
|
||||
icon: '<i class="fa fa-table"></i>'
|
||||
icon: '<i class="fa fa-table"></i>',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'multiBarChart',
|
||||
name: 'Bar Chart',
|
||||
icon: '<i class="fa fa-bar-chart"></i>',
|
||||
transformation: 'pivot'
|
||||
transformation: 'pivot',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'pieChart',
|
||||
name: 'Pie Chart',
|
||||
icon: '<i class="fa fa-pie-chart"></i>',
|
||||
transformation: 'pivot'
|
||||
transformation: 'pivot',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'stackedAreaChart',
|
||||
name: 'Area Chart',
|
||||
icon: '<i class="fa fa-area-chart"></i>',
|
||||
transformation: 'pivot'
|
||||
transformation: 'pivot',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'lineChart',
|
||||
name: 'Line Chart',
|
||||
icon: '<i class="fa fa-line-chart"></i>',
|
||||
transformation: 'pivot'
|
||||
transformation: 'pivot',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'scatterChart',
|
||||
name: 'Scatter Chart',
|
||||
icon: '<i class="cf cf-scatter-chart"></i>'
|
||||
icon: '<i class="cf cf-scatter-chart"></i>',
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
},
|
||||
{
|
||||
id: 'network',
|
||||
name: 'Network',
|
||||
icon: '<i class="fa fa-share-alt"></i>',
|
||||
supports: [DefaultDisplayType.NETWORK]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -104,6 +117,10 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
'scatterChart': {
|
||||
class: ScatterchartVisualization,
|
||||
instance: undefined
|
||||
},
|
||||
'network': {
|
||||
class: NetworkVisualization,
|
||||
instance: undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +176,8 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
$scope.builtInTableDataVisualizationList.push({
|
||||
id: vis.id,
|
||||
name: vis.name,
|
||||
icon: $sce.trustAsHtml(vis.icon)
|
||||
icon: $sce.trustAsHtml(vis.icon),
|
||||
supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
|
||||
})
|
||||
builtInVisualizations[vis.id] = {
|
||||
class: vis.class
|
||||
|
|
@ -253,18 +271,23 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
// enable only when it is last result
|
||||
enableHelium = (index === paragraphRef.results.msg.length - 1)
|
||||
|
||||
if ($scope.type === 'TABLE') {
|
||||
tableData = new TableData()
|
||||
if ($scope.type === 'TABLE' || $scope.type === 'NETWORK') {
|
||||
tableData = new DatasetFactory().createDataset($scope.type)
|
||||
tableData.loadParagraphResult({type: $scope.type, msg: data})
|
||||
$scope.tableDataColumns = tableData.columns
|
||||
$scope.tableDataComment = tableData.comment
|
||||
if ($scope.type === 'NETWORK') {
|
||||
$scope.networkNodes = tableData.networkNodes
|
||||
$scope.networkRelationships = tableData.networkRelationships
|
||||
$scope.networkProperties = tableData.networkProperties
|
||||
}
|
||||
} else if ($scope.type === 'IMG') {
|
||||
$scope.imageData = data
|
||||
}
|
||||
}
|
||||
|
||||
$scope.createDisplayDOMId = function (baseDOMId, type) {
|
||||
if (type === DefaultDisplayType.TABLE) {
|
||||
if (type === DefaultDisplayType.TABLE || type === DefaultDisplayType.NETWORK) {
|
||||
return `${baseDOMId}_graph`
|
||||
} else if (type === DefaultDisplayType.HTML) {
|
||||
return `${baseDOMId}_html`
|
||||
|
|
@ -281,7 +304,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, $routeParams, $locatio
|
|||
|
||||
$scope.renderDefaultDisplay = function (targetElemId, type, data, refresh) {
|
||||
const afterLoaded = () => {
|
||||
if (type === DefaultDisplayType.TABLE) {
|
||||
if (type === DefaultDisplayType.TABLE || type === DefaultDisplayType.NETWORK) {
|
||||
renderGraph(targetElemId, $scope.graphMode, refresh)
|
||||
} else if (type === DefaultDisplayType.HTML) {
|
||||
renderHtml(targetElemId, data)
|
||||
|
|
|
|||
|
|
@ -57,3 +57,59 @@
|
|||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* D3 Graph Configuration */
|
||||
marker {
|
||||
fill: #D3D3D3;
|
||||
}
|
||||
path.link {
|
||||
fill: none;
|
||||
stroke-width: 3px;
|
||||
stroke: #D3D3D3;
|
||||
}
|
||||
path.textpath {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
text.shadow {
|
||||
stroke: #fff;
|
||||
stroke-width: 3px;
|
||||
stroke-opacity: .8;
|
||||
}
|
||||
text.nodeLabel {
|
||||
font-size: 1em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* D3 Graph Configuration */
|
||||
marker {
|
||||
fill: #D3D3D3;
|
||||
}
|
||||
path.link {
|
||||
fill: none;
|
||||
stroke-width: 3px;
|
||||
stroke: #D3D3D3;
|
||||
}
|
||||
path.textpath {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
text.shadow {
|
||||
stroke: #fff;
|
||||
stroke-width: 3px;
|
||||
stroke-opacity: .8;
|
||||
}
|
||||
text.nodeLabel {
|
||||
font-size: 1em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||
resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{type}}"}'
|
||||
resizable on-resize="resize(width, height);">
|
||||
|
||||
<div ng-if="type=='TABLE'"
|
||||
<div ng-if="type=='TABLE' || type == 'NETWORK'"
|
||||
ng-style="getPointerEvent()">
|
||||
<!-- setting -->
|
||||
<div class="option lightBold" style="overflow: visible;"
|
||||
|
|
@ -36,6 +36,26 @@ limitations under the License.
|
|||
ng-show="graphMode == viz.id"></div>
|
||||
</div>
|
||||
|
||||
<div id="p{{id}}_network_header"
|
||||
ng-if="type == 'NETWORK' && graphMode == 'network' && networkNodes != null">
|
||||
<ul class="list-inline">
|
||||
<li>Nodes <span class="badge">{{networkNodes.count}}</span>:</li>
|
||||
<li ng-repeat="(labelName, labelColor) in networkNodes.labels" style="padding: 0">
|
||||
<span style="background-color: {{labelColor}} !important;" class="label label-default network-badge">
|
||||
{{labelName}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-inline">
|
||||
<li ng-if="networkRelationships != null">Relationships <span class="badge">{{networkRelationships.count}}</span>:</li>
|
||||
<li ng-repeat="type in networkRelationships.types" style="padding: 0">
|
||||
<span class="label label-default network-badge">
|
||||
{{type}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- graph -->
|
||||
<div id="p{{id}}_graph"
|
||||
class="graphContainer"
|
||||
|
|
@ -46,6 +66,12 @@ limitations under the License.
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="p{{id}}_network_footer"
|
||||
ng-if="type == 'NETWORK' && graphMode == 'network'">
|
||||
<ul class="list-inline">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="{{id}}_comment"
|
||||
class="text"
|
||||
ng-if="tableDataComment"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const DefaultDisplayType = {
|
|||
HTML: 'HTML',
|
||||
ANGULAR: 'ANGULAR',
|
||||
TEXT: 'TEXT',
|
||||
NETWORK: 'NETWORK'
|
||||
}
|
||||
|
||||
export const DefaultDisplayMagic = {
|
||||
|
|
@ -29,6 +30,7 @@ export const DefaultDisplayMagic = {
|
|||
'%html': DefaultDisplayType.HTML,
|
||||
'%angular': DefaultDisplayType.ANGULAR,
|
||||
'%text': DefaultDisplayType.TEXT,
|
||||
'%network': DefaultDisplayType.NETWORK,
|
||||
}
|
||||
|
||||
export class DataWithType {
|
||||
|
|
|
|||
36
zeppelin-web/src/app/tabledata/dataset.js
Normal file
36
zeppelin-web/src/app/tabledata/dataset.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The abstract dataset rapresentation
|
||||
*/
|
||||
class Dataset {
|
||||
/**
|
||||
* Load the paragraph result, every Dataset implementation must override this method
|
||||
* where is contained the business rules to convert the paragraphResult object to the related dataset type
|
||||
*/
|
||||
loadParagraphResult(paragraphResult) {
|
||||
// override this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataset types
|
||||
*/
|
||||
const DatasetType = Object.freeze({
|
||||
NETWORK: 'NETWORK',
|
||||
TABLE: 'TABLE'
|
||||
})
|
||||
|
||||
export {Dataset, DatasetType}
|
||||
33
zeppelin-web/src/app/tabledata/datasetfactory.js
Normal file
33
zeppelin-web/src/app/tabledata/datasetfactory.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import TableData from './tabledata'
|
||||
import NetworkData from './networkdata'
|
||||
import {DatasetType} from './dataset'
|
||||
|
||||
/**
|
||||
* Create table data object from paragraph table type result
|
||||
*/
|
||||
export default class DatasetFactory {
|
||||
createDataset(datasetType) {
|
||||
switch (datasetType) {
|
||||
case DatasetType.NETWORK:
|
||||
return new NetworkData()
|
||||
case DatasetType.TABLE:
|
||||
return new TableData()
|
||||
default:
|
||||
throw new Error('Dataset type not found')
|
||||
}
|
||||
}
|
||||
}
|
||||
46
zeppelin-web/src/app/tabledata/datasetfactory.test.js
Normal file
46
zeppelin-web/src/app/tabledata/datasetfactory.test.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import NetworkData from './networkdata.js'
|
||||
import TableData from './tabledata.js'
|
||||
import {DatasetType} from './dataset.js'
|
||||
import DatasetFactory from './datasetfactory.js'
|
||||
|
||||
describe('DatasetFactory build', function() {
|
||||
let df
|
||||
|
||||
beforeAll(function() {
|
||||
df = new DatasetFactory()
|
||||
})
|
||||
|
||||
it('should create a TableData instance', function() {
|
||||
let td = df.createDataset(DatasetType.TABLE)
|
||||
expect(td instanceof TableData).toBeTruthy()
|
||||
expect(td.columns.length).toBe(0)
|
||||
expect(td.rows.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should create a NetworkData instance', function() {
|
||||
let nd = df.createDataset(DatasetType.NETWORK)
|
||||
expect(nd instanceof NetworkData).toBeTruthy()
|
||||
expect(nd.columns.length).toBe(0)
|
||||
expect(nd.rows.length).toBe(0)
|
||||
expect(nd.graph).toEqual({})
|
||||
})
|
||||
|
||||
it('should thrown an Error', function() {
|
||||
expect(function() { df.createDataset('text') })
|
||||
.toThrow(new Error('Dataset type not found'))
|
||||
})
|
||||
})
|
||||
48
zeppelin-web/src/app/tabledata/network.js
Normal file
48
zeppelin-web/src/app/tabledata/network.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Transformation from './transformation'
|
||||
|
||||
/**
|
||||
* trasformation settings for network visualization
|
||||
*/
|
||||
export default class NetworkTransformation extends Transformation {
|
||||
getSetting() {
|
||||
let self = this
|
||||
let configObj = self.config
|
||||
return {
|
||||
template: 'app/tabledata/network_settings.html',
|
||||
scope: {
|
||||
config: configObj,
|
||||
isEmptyObject: function(obj) {
|
||||
obj = obj || {}
|
||||
return angular.equals(obj, {})
|
||||
},
|
||||
setNetworkLabel: function(label, value) {
|
||||
configObj.properties[label].selected = value
|
||||
},
|
||||
saveConfig: function() {
|
||||
self.emitConfig(configObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
}
|
||||
|
||||
transform(networkData) {
|
||||
return networkData
|
||||
}
|
||||
}
|
||||
74
zeppelin-web/src/app/tabledata/network_settings.html
Normal file
74
zeppelin-web/src/app/tabledata/network_settings.html
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<div class="row">
|
||||
<form>
|
||||
<fieldset class=" col-xs-12">
|
||||
<h4>Force Layout settings</h4>
|
||||
<div class="form-check col-xs-4">
|
||||
<label for="{{$id}}_timeout">Stop Force layout after</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" ng-model="config.d3Graph.forceLayout.timeout" id="{{$id}}_timeout" />
|
||||
<span class="input-group-addon">ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check col-xs-4">
|
||||
<div class="form-group">
|
||||
<label for="{{$id}}_charge">Force Layout Charge</label>
|
||||
<input type="text" class="form-control" ng-model="config.d3Graph.forceLayout.charge" id="{{$id}}_charge" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check col-xs-4">
|
||||
<div class="form-group">
|
||||
<label for="{{$id}}_linkDistance">Force Layout Link Distance</label>
|
||||
<input type="text" class="form-control" ng-model="config.d3Graph.forceLayout.linkDistance" id="{{$id}}_linkDistance" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class=" col-xs-12">
|
||||
<h4>Globals</h4>
|
||||
<div class="form-check col-xs-4">
|
||||
<div class="form-group">
|
||||
<label for="{{$id}}_charge">Minimum scale to show node and edge labels</label>
|
||||
<input type="text" class="form-control" ng-model="config.d3Graph.zoom.minScale" id="{{$id}}_minScale" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-group col-xs-12">
|
||||
<h4>Choose node labels</h4>
|
||||
<div ng-if="isEmptyObject(config.properties)">
|
||||
No labels to set
|
||||
</div>
|
||||
<div class="btn-group network-labels network-badge-settings"
|
||||
ng-repeat="(key, value) in config.properties track by key">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{key}}:<i>{{config.properties[key].selected}}</i> <div class="caret"></div>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="val in value.keys" ng-click="setNetworkLabel(key, val)">
|
||||
<a><i ng-if="config.properties[key].selected == val" class="glyphicon glyphicon-ok">
|
||||
</i> {{val}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="form-group col-xs-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-click="saveConfig()">
|
||||
<span class="glyphicon glyphicon-floppy-disk"></span>
|
||||
Save configuration
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
145
zeppelin-web/src/app/tabledata/networkdata.js
Normal file
145
zeppelin-web/src/app/tabledata/networkdata.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import TableData from './tabledata'
|
||||
import {DatasetType} from './dataset'
|
||||
|
||||
/**
|
||||
* Create network data object from paragraph graph type result
|
||||
*/
|
||||
export default class NetworkData extends TableData {
|
||||
constructor(graph) {
|
||||
super()
|
||||
this.graph = graph || {}
|
||||
if (this.graph.nodes) {
|
||||
this.loadParagraphResult({msg: JSON.stringify(graph), type: DatasetType.NETWORK})
|
||||
}
|
||||
}
|
||||
|
||||
loadParagraphResult(paragraphResult) {
|
||||
if (!paragraphResult || paragraphResult.type !== DatasetType.NETWORK) {
|
||||
console.log('Can not load paragraph result')
|
||||
return
|
||||
}
|
||||
|
||||
this.graph = JSON.parse(paragraphResult.msg.trim() || '{}')
|
||||
|
||||
if (!this.graph.nodes) {
|
||||
console.log('Graph result is empty')
|
||||
return
|
||||
}
|
||||
|
||||
this.setNodesDefaults()
|
||||
this.setEdgesDefaults()
|
||||
|
||||
this.networkNodes = angular.equals({}, this.graph.labels || {})
|
||||
? null : {count: this.graph.nodes.length, labels: this.graph.labels}
|
||||
this.networkRelationships = angular.equals([], this.graph.types || [])
|
||||
? null : {count: this.graph.edges.length, types: this.graph.types}
|
||||
|
||||
let rows = []
|
||||
let comment = ''
|
||||
let entities = this.graph.nodes.concat(this.graph.edges)
|
||||
let baseColumnNames = [{name: 'id', index: 0, aggr: 'sum'},
|
||||
{name: 'label', index: 1, aggr: 'sum'}]
|
||||
let internalFieldsToJump = ['count', 'size', 'totalCount',
|
||||
'data', 'x', 'y', 'labels']
|
||||
let baseCols = _.map(baseColumnNames, function(col) { return col.name })
|
||||
let keys = _.map(entities, function(elem) { return Object.keys(elem.data || {}) })
|
||||
keys = _.flatten(keys)
|
||||
keys = _.uniq(keys).filter(function(key) {
|
||||
return baseCols.indexOf(key) === -1
|
||||
})
|
||||
let columnNames = baseColumnNames.concat(_.map(keys, function(elem, i) {
|
||||
return {name: elem, index: i + baseColumnNames.length, aggr: 'sum'}
|
||||
}))
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
let entity = entities[i]
|
||||
let col = []
|
||||
let col2 = []
|
||||
entity.data = entity.data || {}
|
||||
for (let j = 0; j < columnNames.length; j++) {
|
||||
let name = columnNames[j].name
|
||||
let value = name in entity && internalFieldsToJump.indexOf(name) === -1
|
||||
? entity[name] : entity.data[name]
|
||||
let parsedValue = value === null || value === undefined ? '' : value
|
||||
col.push(parsedValue)
|
||||
col2.push({key: name, value: parsedValue})
|
||||
}
|
||||
rows.push(col)
|
||||
}
|
||||
|
||||
this.comment = comment
|
||||
this.columns = columnNames
|
||||
this.rows = rows
|
||||
}
|
||||
|
||||
setNodesDefaults() {
|
||||
}
|
||||
|
||||
setEdgesDefaults() {
|
||||
this.graph.edges
|
||||
.sort((a, b) => {
|
||||
if (a.source > b.source) {
|
||||
return 1
|
||||
} else if (a.source < b.source) {
|
||||
return -1
|
||||
} else if (a.target > b.target) {
|
||||
return 1
|
||||
} else if (a.target < b.target) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
this.graph.edges
|
||||
.forEach((edge, index) => {
|
||||
let prevEdge = this.graph.edges[index - 1]
|
||||
edge.count = (index > 0 && +edge.source === +prevEdge.source && +edge.target === +prevEdge.target
|
||||
? prevEdge.count : 0) + 1
|
||||
edge.totalCount = this.graph.edges
|
||||
.filter((innerEdge) => +edge.source === +innerEdge.source && +edge.target === +innerEdge.target)
|
||||
.length
|
||||
})
|
||||
this.graph.edges
|
||||
.forEach((edge) => {
|
||||
if (typeof +edge.source === 'number') {
|
||||
edge.source = this.graph.nodes.filter((node) => +edge.source === +node.id)[0] || null
|
||||
}
|
||||
if (typeof +edge.target === 'number') {
|
||||
edge.target = this.graph.nodes.filter((node) => +edge.target === +node.id)[0] || null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getNetworkProperties() {
|
||||
let baseCols = ['id', 'label']
|
||||
let properties = {}
|
||||
this.graph.nodes.forEach(function(node) {
|
||||
let hasLabel = 'label' in node && node.label !== ''
|
||||
if (!hasLabel) {
|
||||
return
|
||||
}
|
||||
let label = node.label
|
||||
let hasKey = hasLabel && label in properties
|
||||
let keys = _.uniq(Object.keys(node.data || {})
|
||||
.concat(hasKey ? properties[label].keys : baseCols))
|
||||
if (!hasKey) {
|
||||
properties[label] = {selected: 'label'}
|
||||
}
|
||||
properties[label].keys = keys
|
||||
})
|
||||
return properties
|
||||
}
|
||||
}
|
||||
46
zeppelin-web/src/app/tabledata/networkdata.test.js
Normal file
46
zeppelin-web/src/app/tabledata/networkdata.test.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import NetworkData from './networkdata.js'
|
||||
import {DatasetType} from './dataset.js'
|
||||
|
||||
describe('NetworkData build', function() {
|
||||
let nd
|
||||
|
||||
beforeEach(function() {
|
||||
nd = new NetworkData()
|
||||
})
|
||||
|
||||
it('should initialize the default value', function() {
|
||||
expect(nd.columns.length).toBe(0)
|
||||
expect(nd.rows.length).toBe(0)
|
||||
expect(nd.graph).toEqual({})
|
||||
})
|
||||
|
||||
it('should able to create NetowkData from paragraph result', function() {
|
||||
let jsonExpected = {nodes: [{id: 1}, {id: 2}], edges: [{source: 2, target: 1, id: 1}]}
|
||||
nd.loadParagraphResult({
|
||||
type: DatasetType.NETWORK,
|
||||
msg: JSON.stringify(jsonExpected)
|
||||
})
|
||||
|
||||
expect(nd.columns.length).toBe(2)
|
||||
expect(nd.rows.length).toBe(3)
|
||||
expect(nd.graph.nodes[0].id).toBe(jsonExpected.nodes[0].id)
|
||||
expect(nd.graph.nodes[1].id).toBe(jsonExpected.nodes[1].id)
|
||||
expect(nd.graph.edges[0].id).toBe(jsonExpected.edges[0].id)
|
||||
expect(nd.graph.edges[0].source.id).toBe(jsonExpected.nodes[1].id)
|
||||
expect(nd.graph.edges[0].target.id).toBe(jsonExpected.nodes[0].id)
|
||||
})
|
||||
})
|
||||
|
|
@ -11,19 +11,21 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {Dataset, DatasetType} from './dataset'
|
||||
|
||||
/**
|
||||
* Create table data object from paragraph table type result
|
||||
*/
|
||||
export default class TableData {
|
||||
export default class TableData extends Dataset {
|
||||
constructor (columns, rows, comment) {
|
||||
super()
|
||||
this.columns = columns || []
|
||||
this.rows = rows || []
|
||||
this.comment = comment || ''
|
||||
}
|
||||
|
||||
loadParagraphResult (paragraphResult) {
|
||||
if (!paragraphResult || paragraphResult.type !== 'TABLE') {
|
||||
if (!paragraphResult || paragraphResult.type !== DatasetType.TABLE) {
|
||||
console.log('Can not load paragraph result')
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Visualization from '../visualization'
|
||||
import NetworkTransformation from '../../tabledata/network'
|
||||
|
||||
/**
|
||||
* Visualize data in network format
|
||||
*/
|
||||
export default class NetworkVisualization extends Visualization {
|
||||
constructor(targetEl, config) {
|
||||
super(targetEl, config)
|
||||
console.log('Init network viz')
|
||||
if (!config.properties) {
|
||||
config.properties = {}
|
||||
}
|
||||
if (!config.d3Graph) {
|
||||
config.d3Graph = {
|
||||
forceLayout: {
|
||||
timeout: 10000,
|
||||
charge: -120,
|
||||
linkDistance: 80,
|
||||
},
|
||||
zoom: {
|
||||
minScale: 1.3
|
||||
}
|
||||
}
|
||||
}
|
||||
this.targetEl.addClass('network')
|
||||
this.containerId = this.targetEl.prop('id')
|
||||
this.force = null
|
||||
this.svg = null
|
||||
this.$timeout = angular.injector(['ng']).get('$timeout')
|
||||
this.$interpolate = angular.injector(['ng']).get('$interpolate')
|
||||
this.transformation = new NetworkTransformation(config)
|
||||
}
|
||||
|
||||
refresh() {
|
||||
console.log('refresh')
|
||||
}
|
||||
|
||||
render(networkData) {
|
||||
if (!('graph' in networkData)) {
|
||||
console.log('graph not found')
|
||||
return
|
||||
}
|
||||
console.log('Render Graph Visualization')
|
||||
|
||||
let transformationConfig = this.transformation.getSetting().scope.config
|
||||
console.log('cfg', transformationConfig)
|
||||
if (transformationConfig && angular.equals({}, transformationConfig.properties)) {
|
||||
transformationConfig.properties = networkData.getNetworkProperties()
|
||||
}
|
||||
|
||||
this.targetEl.empty().append('<svg></svg>')
|
||||
|
||||
let width = this.targetEl.width()
|
||||
let height = this.targetEl.height()
|
||||
let self = this
|
||||
let defaultOpacity = 0
|
||||
let nodeSize = 10
|
||||
let textOffset = 3
|
||||
let linkSize = 10
|
||||
|
||||
let arcPath = (leftHand, d) => {
|
||||
let start = leftHand ? d.source : d.target
|
||||
let end = leftHand ? d.target : d.source
|
||||
let dx = end.x - start.x
|
||||
let dy = end.y - start.y
|
||||
let dr = d.totalCount === 1
|
||||
? 0 : Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) / (1 + (1 / d.totalCount) * (d.count - 1))
|
||||
let sweep = leftHand ? 0 : 1
|
||||
return `M${start.x},${start.y}A${dr},${dr} 0 0,${sweep} ${end.x},${end.y}`
|
||||
}
|
||||
// Use elliptical arc path segments to doubly-encode directionality.
|
||||
let tick = () => {
|
||||
// Links
|
||||
linkPath.attr('d', function(d) {
|
||||
return arcPath(true, d)
|
||||
})
|
||||
textPath.attr('d', function(d) {
|
||||
return arcPath(d.source.x < d.target.x, d)
|
||||
})
|
||||
// Nodes
|
||||
circle.attr('transform', (d) => `translate(${d.x},${d.y})`)
|
||||
text.attr('transform', (d) => `translate(${d.x},${d.y})`)
|
||||
}
|
||||
|
||||
let setOpacity = (scale) => {
|
||||
let opacity = scale >= +transformationConfig.d3Graph.zoom.minScale ? 1 : 0
|
||||
this.svg.selectAll('.nodeLabel')
|
||||
.style('opacity', opacity)
|
||||
this.svg.selectAll('textPath')
|
||||
.style('opacity', opacity)
|
||||
}
|
||||
|
||||
let zoom = d3.behavior.zoom()
|
||||
.scaleExtent([1, 10])
|
||||
.on('zoom', () => {
|
||||
console.log('zoom')
|
||||
setOpacity(d3.event.scale)
|
||||
container.attr('transform', `translate(${d3.event.translate})scale(${d3.event.scale})`)
|
||||
})
|
||||
|
||||
this.svg = d3.select(`#${this.containerId} svg`)
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.call(zoom)
|
||||
|
||||
this.force = d3.layout.force()
|
||||
.charge(transformationConfig.d3Graph.forceLayout.charge)
|
||||
.linkDistance(transformationConfig.d3Graph.forceLayout.linkDistance)
|
||||
.on('tick', tick)
|
||||
.nodes(networkData.graph.nodes)
|
||||
.links(networkData.graph.edges)
|
||||
.size([width, height])
|
||||
.on('start', () => {
|
||||
console.log('force layout start')
|
||||
this.$timeout(() => { this.force.stop() }, transformationConfig.d3Graph.forceLayout.timeout)
|
||||
})
|
||||
.on('end', () => {
|
||||
console.log('force layout stop')
|
||||
setOpacity(zoom.scale())
|
||||
})
|
||||
.start()
|
||||
|
||||
let renderFooterOnClick = (entity, type) => {
|
||||
let footerId = this.containerId + '_footer'
|
||||
let obj = {id: entity.id, label: entity.defaultLabel || entity.label, type: type}
|
||||
let html = [this.$interpolate(['<li><b>{{type}}_id:</b> {{id}}</li>',
|
||||
'<li><b>{{type}}_type:</b> {{label}}</li>'].join(''))(obj)]
|
||||
html = html.concat(_.map(entity.data, (v, k) => {
|
||||
return this.$interpolate('<li><b>{{field}}:</b> {{value}}</li>')({field: k, value: v})
|
||||
}))
|
||||
angular.element('#' + footerId)
|
||||
.find('.list-inline')
|
||||
.empty()
|
||||
.append(html.join(''))
|
||||
}
|
||||
|
||||
let drag = d3.behavior.drag()
|
||||
.origin((d) => d)
|
||||
.on('dragstart', function(d) {
|
||||
console.log('dragstart')
|
||||
d3.event.sourceEvent.stopPropagation()
|
||||
d3.select(this).classed('dragging', true)
|
||||
self.force.stop()
|
||||
})
|
||||
.on('drag', function(d) {
|
||||
console.log('drag')
|
||||
d.px += d3.event.dx
|
||||
d.py += d3.event.dy
|
||||
d.x += d3.event.dx
|
||||
d.y += d3.event.dy
|
||||
})
|
||||
.on('dragend', function(d) {
|
||||
console.log('dragend')
|
||||
d.fixed = true
|
||||
d3.select(this).classed('dragging', false)
|
||||
self.force.resume()
|
||||
})
|
||||
|
||||
let container = this.svg.append('g')
|
||||
if (networkData.graph.directed) {
|
||||
container.append('svg:defs').selectAll('marker')
|
||||
.data(['arrowMarker-' + this.containerId])
|
||||
.enter()
|
||||
.append('svg:marker')
|
||||
.attr('id', String)
|
||||
.attr('viewBox', '0 -5 10 10')
|
||||
.attr('refX', 16)
|
||||
.attr('refY', 0)
|
||||
.attr('markerWidth', 4)
|
||||
.attr('markerHeight', 4)
|
||||
.attr('orient', 'auto')
|
||||
.append('svg:path')
|
||||
.attr('d', 'M0,-5L10,0L0,5')
|
||||
}
|
||||
// Links
|
||||
let link = container.append('svg:g')
|
||||
.on('click', () => {
|
||||
renderFooterOnClick(d3.select(d3.event.target).datum(), 'edge')
|
||||
})
|
||||
.selectAll('g.link')
|
||||
.data(self.force.links())
|
||||
.enter()
|
||||
.append('g')
|
||||
let getPathId = (d) => this.containerId + '_' + d.source.index + '_' + d.target.index + '_' + d.count
|
||||
let showLabel = (d) => this._showNodeLabel(d)
|
||||
let linkPath = link.append('svg:path')
|
||||
.attr('class', 'link')
|
||||
.attr('size', linkSize)
|
||||
.attr('marker-end', `url(#arrowMarker-${this.containerId})`)
|
||||
let textPath = link.append('svg:path')
|
||||
.attr('id', getPathId)
|
||||
.attr('class', 'textpath')
|
||||
container.append('svg:g')
|
||||
.selectAll('.pathLabel')
|
||||
.data(self.force.links())
|
||||
.enter()
|
||||
.append('svg:text')
|
||||
.attr('class', 'pathLabel')
|
||||
.append('svg:textPath')
|
||||
.attr('startOffset', '50%')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('xlink:href', (d) => '#' + getPathId(d))
|
||||
.text((d) => d.label)
|
||||
.style('opacity', defaultOpacity)
|
||||
// Nodes
|
||||
let circle = container.append('svg:g')
|
||||
.on('click', () => {
|
||||
renderFooterOnClick(d3.select(d3.event.target).datum(), 'node')
|
||||
})
|
||||
.selectAll('circle')
|
||||
.data(self.force.nodes())
|
||||
.enter().append('svg:circle')
|
||||
.attr('r', (d) => nodeSize)
|
||||
.attr('fill', (d) => networkData.graph.labels && d.label in networkData.graph.labels
|
||||
? networkData.graph.labels[d.label] : '#000000')
|
||||
.call(drag)
|
||||
let text = container.append('svg:g').selectAll('g')
|
||||
.data(self.force.nodes())
|
||||
.enter().append('svg:g')
|
||||
text.append('svg:text')
|
||||
.attr('x', (d) => nodeSize + textOffset)
|
||||
.attr('size', nodeSize)
|
||||
.attr('y', '.31em')
|
||||
.attr('class', (d) => 'nodeLabel shadow label-' + d.label)
|
||||
.text(showLabel)
|
||||
.style('opacity', defaultOpacity)
|
||||
text.append('svg:text')
|
||||
.attr('x', (d) => nodeSize + textOffset)
|
||||
.attr('size', nodeSize)
|
||||
.attr('y', '.31em')
|
||||
.attr('class', (d) => 'nodeLabel label-' + d.label)
|
||||
.text(showLabel)
|
||||
.style('opacity', defaultOpacity)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
_showNodeLabel(d) {
|
||||
let transformationConfig = this.transformation.getSetting().scope.config
|
||||
let selectedLabel = (transformationConfig.properties[d.label] || {selected: 'label'}).selected
|
||||
return d.data[selectedLabel] || d[selectedLabel]
|
||||
}
|
||||
|
||||
getTransformation() {
|
||||
return this.transformation
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,7 @@ export default class TableVisualization extends Visualization {
|
|||
|
||||
columnDefs: columnNames.map(colName => {
|
||||
return {
|
||||
displayName: colName,
|
||||
name: colName,
|
||||
type: DefaultTableColumnType,
|
||||
cellTemplate: `
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function NavCtrl ($scope, $rootScope, $http, $routeParams, $location,
|
|||
function getZeppelinVersion () {
|
||||
$http.get(baseUrlSrv.getRestApiBase() + '/version').success(
|
||||
function (data, status, headers, config) {
|
||||
$rootScope.zeppelinVersion = data.body
|
||||
$rootScope.zeppelinVersion = data.body.version
|
||||
}).error(
|
||||
function (data, status, headers, config) {
|
||||
console.log('Error %o %o', status, data.message)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function resizable () {
|
|||
let colStep = window.innerWidth / 12
|
||||
elem.off('resizestop')
|
||||
let conf = angular.copy(resizableConfig)
|
||||
if (resize.graphType === 'TABLE' || resize.graphType === 'TEXT') {
|
||||
if (resize.graphType === 'TABLE' || resize.graphType === 'NETWORK' || resize.graphType === 'TEXT') {
|
||||
conf.grid = [colStep, 10]
|
||||
conf.minHeight = 100
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -424,8 +424,16 @@ public class ZeppelinConfiguration extends XMLConfiguration {
|
|||
return getRelativeDir(ConfVars.ZEPPELIN_HELIUM_REGISTRY);
|
||||
}
|
||||
|
||||
public String getHeliumNpmRegistry() {
|
||||
return getString(ConfVars.ZEPPELIN_HELIUM_NPM_REGISTRY);
|
||||
public String getHeliumNodeInstallerUrl() {
|
||||
return getString(ConfVars.ZEPPELIN_HELIUM_NODE_INSTALLER_URL);
|
||||
}
|
||||
|
||||
public String getHeliumNpmInstallerUrl() {
|
||||
return getString(ConfVars.ZEPPELIN_HELIUM_NPM_INSTALLER_URL);
|
||||
}
|
||||
|
||||
public String getHeliumYarnInstallerUrl() {
|
||||
return getString(ConfVars.ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL);
|
||||
}
|
||||
|
||||
public String getNotebookAuthorizationPath() {
|
||||
|
|
@ -643,7 +651,12 @@ public class ZeppelinConfiguration extends XMLConfiguration {
|
|||
ZEPPELIN_CONF_DIR("zeppelin.conf.dir", "conf"),
|
||||
ZEPPELIN_DEP_LOCALREPO("zeppelin.dep.localrepo", "local-repo"),
|
||||
ZEPPELIN_HELIUM_REGISTRY("zeppelin.helium.registry", "helium," + HELIUM_PACKAGE_DEFAULT_URL),
|
||||
ZEPPELIN_HELIUM_NPM_REGISTRY("zeppelin.helium.npm.registry", "http://registry.npmjs.org/"),
|
||||
ZEPPELIN_HELIUM_NODE_INSTALLER_URL("zeppelin.helium.node.installer.url",
|
||||
"https://nodejs.org/dist/"),
|
||||
ZEPPELIN_HELIUM_NPM_INSTALLER_URL("zeppelin.helium.npm.installer.url",
|
||||
"http://registry.npmjs.org/"),
|
||||
ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL("zeppelin.helium.yarnpkg.installer.url",
|
||||
"https://github.com/yarnpkg/yarn/releases/download/"),
|
||||
// Allows a way to specify a ',' separated list of allowed origins for rest and websockets
|
||||
// i.e. http://localhost:8080
|
||||
ZEPPELIN_ALLOWED_ORIGINS("zeppelin.server.allowed.origins", "*"),
|
||||
|
|
|
|||
|
|
@ -48,24 +48,24 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration;
|
|||
* Load helium visualization & spell
|
||||
*/
|
||||
public class HeliumBundleFactory {
|
||||
Logger logger = LoggerFactory.getLogger(HeliumBundleFactory.class);
|
||||
private final String NODE_VERSION = "v6.9.1";
|
||||
private final String NPM_VERSION = "3.10.8";
|
||||
private final String YARN_VERSION = "v0.21.3";
|
||||
private Logger logger = LoggerFactory.getLogger(HeliumBundleFactory.class);
|
||||
private static final String NODE_VERSION = "v6.9.1";
|
||||
private static final String NPM_VERSION = "3.10.8";
|
||||
private static final String YARN_VERSION = "v0.21.3";
|
||||
private static final String NPM_PACKAGE_NAME = "npm";
|
||||
public static final String HELIUM_LOCAL_REPO = "helium-bundle";
|
||||
public static final String HELIUM_BUNDLES_DIR = "bundles";
|
||||
public static final String HELIUM_LOCAL_MODULE_DIR = "local_modules";
|
||||
public static final String HELIUM_BUNDLES_SRC_DIR = "src";
|
||||
public static final String HELIUM_BUNDLES_SRC = "load.js";
|
||||
public static final String YARN_CACHE_DIR = "yarn-cache";
|
||||
public static final String PACKAGE_JSON = "package.json";
|
||||
public static final String HELIUM_BUNDLE_CACHE = "helium.bundle.cache.js";
|
||||
public static final String HELIUM_BUNDLE = "helium.bundle.js";
|
||||
public static final String HELIUM_BUNDLES_VAR = "heliumBundles";
|
||||
private final int FETCH_RETRY_COUNT = 2;
|
||||
private final int FETCH_RETRY_FACTOR_COUNT = 1;
|
||||
private final int FETCH_RETRY_MIN_TIMEOUT = 5000; // Milliseconds
|
||||
protected static final String HELIUM_LOCAL_REPO = "helium-bundle";
|
||||
private static final String HELIUM_BUNDLES_DIR = "bundles";
|
||||
private static final String HELIUM_LOCAL_MODULE_DIR = "local_modules";
|
||||
private static final String HELIUM_BUNDLES_SRC_DIR = "src";
|
||||
private static final String HELIUM_BUNDLES_SRC = "load.js";
|
||||
private static final String YARN_CACHE_DIR = "yarn-cache";
|
||||
private static final String PACKAGE_JSON = "package.json";
|
||||
private static final String HELIUM_BUNDLE_CACHE = "helium.bundle.cache.js";
|
||||
private static final String HELIUM_BUNDLE = "helium.bundle.js";
|
||||
private static final String HELIUM_BUNDLES_VAR = "heliumBundles";
|
||||
private static final int FETCH_RETRY_COUNT = 2;
|
||||
private static final int FETCH_RETRY_FACTOR_COUNT = 1;
|
||||
private static final int FETCH_RETRY_MIN_TIMEOUT = 5000; // Milliseconds
|
||||
|
||||
private final FrontendPluginFactory frontEndPluginFactory;
|
||||
private final File nodeInstallationDirectory;
|
||||
|
|
@ -73,17 +73,16 @@ public class HeliumBundleFactory {
|
|||
private final File heliumBundleDirectory;
|
||||
private final File heliumLocalModuleDirectory;
|
||||
private final File yarnCacheDir;
|
||||
private ZeppelinConfiguration conf;
|
||||
private File tabledataModulePath;
|
||||
private File visualizationModulePath;
|
||||
private File spellModulePath;
|
||||
private String defaultNodeRegistryUrl;
|
||||
private String defaultNpmRegistryUrl;
|
||||
private String defaultYarnRegistryUrl;
|
||||
private String defaultNodeInstallerUrl;
|
||||
private String defaultNpmInstallerUrl;
|
||||
private String defaultYarnInstallerUrl;
|
||||
private Gson gson;
|
||||
private boolean nodeAndNpmInstalled = false;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
private ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
public HeliumBundleFactory(
|
||||
ZeppelinConfiguration conf,
|
||||
|
|
@ -98,7 +97,7 @@ public class HeliumBundleFactory {
|
|||
this.spellModulePath = spellModulePath;
|
||||
}
|
||||
|
||||
public HeliumBundleFactory(
|
||||
private HeliumBundleFactory(
|
||||
ZeppelinConfiguration conf,
|
||||
File nodeInstallationDir,
|
||||
File moduleDownloadPath) throws TaskRunnerException {
|
||||
|
|
@ -106,11 +105,9 @@ public class HeliumBundleFactory {
|
|||
this.heliumBundleDirectory = new File(heliumLocalRepoDirectory, HELIUM_BUNDLES_DIR);
|
||||
this.heliumLocalModuleDirectory = new File(heliumLocalRepoDirectory, HELIUM_LOCAL_MODULE_DIR);
|
||||
this.yarnCacheDir = new File(heliumLocalRepoDirectory, YARN_CACHE_DIR);
|
||||
this.conf = conf;
|
||||
// To be done in ZEPPELIN-2214: Soft-code installer urls
|
||||
this.defaultNodeRegistryUrl = "https://nodejs.org/dist/";
|
||||
this.defaultNpmRegistryUrl = conf.getHeliumNpmRegistry();
|
||||
this.defaultYarnRegistryUrl = "https://github.com/yarnpkg/yarn/releases/download/";
|
||||
this.defaultNodeInstallerUrl = conf.getHeliumNodeInstallerUrl();
|
||||
this.defaultNpmInstallerUrl = conf.getHeliumNpmInstallerUrl();
|
||||
this.defaultYarnInstallerUrl = conf.getHeliumYarnInstallerUrl();
|
||||
|
||||
nodeInstallationDirectory = (nodeInstallationDir == null) ?
|
||||
heliumLocalRepoDirectory : nodeInstallationDir;
|
||||
|
|
@ -127,21 +124,21 @@ public class HeliumBundleFactory {
|
|||
}
|
||||
try {
|
||||
NodeInstaller nodeInstaller = frontEndPluginFactory
|
||||
.getNodeInstaller(getProxyConfig(isSecure(defaultNodeRegistryUrl)));
|
||||
.getNodeInstaller(getProxyConfig(isSecure(defaultNodeInstallerUrl)));
|
||||
nodeInstaller.setNodeVersion(NODE_VERSION);
|
||||
nodeInstaller.setNodeDownloadRoot(defaultNodeRegistryUrl);
|
||||
nodeInstaller.setNodeDownloadRoot(defaultNodeInstallerUrl);
|
||||
nodeInstaller.install();
|
||||
|
||||
NPMInstaller npmInstaller = frontEndPluginFactory
|
||||
.getNPMInstaller(getProxyConfig(isSecure(defaultNpmRegistryUrl)));
|
||||
.getNPMInstaller(getProxyConfig(isSecure(defaultNpmInstallerUrl)));
|
||||
npmInstaller.setNpmVersion(NPM_VERSION);
|
||||
npmInstaller.setNpmDownloadRoot(defaultNpmRegistryUrl + "/" + NPM_PACKAGE_NAME + "/-/");
|
||||
npmInstaller.setNpmDownloadRoot(defaultNpmInstallerUrl + "/" + NPM_PACKAGE_NAME + "/-/");
|
||||
npmInstaller.install();
|
||||
|
||||
YarnInstaller yarnInstaller = frontEndPluginFactory
|
||||
.getYarnInstaller(getProxyConfig(isSecure(defaultYarnRegistryUrl)));
|
||||
.getYarnInstaller(getProxyConfig(isSecure(defaultYarnInstallerUrl)));
|
||||
yarnInstaller.setYarnVersion(YARN_VERSION);
|
||||
yarnInstaller.setYarnDownloadRoot(defaultYarnRegistryUrl);
|
||||
yarnInstaller.setYarnDownloadRoot(defaultYarnInstallerUrl);
|
||||
yarnInstaller.install();
|
||||
yarnCacheDir.mkdirs();
|
||||
String yarnCacheDirPath = yarnCacheDir.getAbsolutePath();
|
||||
|
|
@ -203,48 +200,47 @@ public class HeliumBundleFactory {
|
|||
buildAllPackages(pkgs, false);
|
||||
}
|
||||
|
||||
public File getHeliumPackageDirectory(String pkgName) {
|
||||
private File getHeliumPackageDirectory(String pkgName) {
|
||||
return new File(heliumBundleDirectory, pkgName);
|
||||
}
|
||||
|
||||
public File getHeliumPackageSourceDirectory(String pkgName) {
|
||||
private File getHeliumPackageSourceDirectory(String pkgName) {
|
||||
return new File(heliumBundleDirectory, pkgName + "/" + HELIUM_BUNDLES_SRC_DIR);
|
||||
}
|
||||
|
||||
public File getHeliumPackageBundleCache(String pkgName) {
|
||||
private File getHeliumPackageBundleCache(String pkgName) {
|
||||
return new File(heliumBundleDirectory, pkgName + "/" + HELIUM_BUNDLE_CACHE);
|
||||
}
|
||||
|
||||
public static List<String> unTgz(File tarFile, File directory) throws IOException {
|
||||
List<String> result = new ArrayList<String>();
|
||||
InputStream is = new FileInputStream(tarFile);
|
||||
GzipCompressorInputStream gcis = new GzipCompressorInputStream(is);
|
||||
TarArchiveInputStream in = new TarArchiveInputStream(gcis);
|
||||
TarArchiveEntry entry = in.getNextTarEntry();
|
||||
while (entry != null) {
|
||||
if (entry.isDirectory()) {
|
||||
private static List<String> unTgz(File tarFile, File directory) throws IOException {
|
||||
List<String> result = new ArrayList<>();
|
||||
try (TarArchiveInputStream in = new TarArchiveInputStream(
|
||||
new GzipCompressorInputStream(new FileInputStream(tarFile)))) {
|
||||
TarArchiveEntry entry = in.getNextTarEntry();
|
||||
while (entry != null) {
|
||||
if (entry.isDirectory()) {
|
||||
entry = in.getNextTarEntry();
|
||||
continue;
|
||||
}
|
||||
File curfile = new File(directory, entry.getName());
|
||||
File parent = curfile.getParentFile();
|
||||
if (!parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
try (OutputStream out = new FileOutputStream(curfile)) {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
result.add(entry.getName());
|
||||
entry = in.getNextTarEntry();
|
||||
continue;
|
||||
}
|
||||
File curfile = new File(directory, entry.getName());
|
||||
File parent = curfile.getParentFile();
|
||||
if (!parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
OutputStream out = new FileOutputStream(curfile);
|
||||
IOUtils.copy(in, out);
|
||||
out.close();
|
||||
result.add(entry.getName());
|
||||
entry = in.getNextTarEntry();
|
||||
}
|
||||
in.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return main file name of this helium package (relative path)
|
||||
*/
|
||||
public String downloadPackage(HeliumPackage pkg, String[] nameAndVersion, File bundleDir,
|
||||
private String downloadPackage(HeliumPackage pkg, String[] nameAndVersion, File bundleDir,
|
||||
String templateWebpackConfig, String templatePackageJson,
|
||||
FrontendPluginFactory fpf) throws IOException, TaskRunnerException {
|
||||
if (bundleDir.exists()) {
|
||||
|
|
@ -279,7 +275,8 @@ public class HeliumBundleFactory {
|
|||
npmCommand(fpf, "pack " + pkg.getArtifact());
|
||||
File extracted = new File(heliumBundleDirectory, "package");
|
||||
FileUtils.deleteDirectory(extracted);
|
||||
unTgz(tgz, heliumBundleDirectory);
|
||||
List<String> entries = unTgz(tgz, heliumBundleDirectory);
|
||||
for (String entry: entries) logger.debug("Extracted " + entry);
|
||||
tgz.delete();
|
||||
FileUtils.copyDirectory(extracted, bundleDir);
|
||||
FileUtils.deleteDirectory(extracted);
|
||||
|
|
@ -324,7 +321,7 @@ public class HeliumBundleFactory {
|
|||
return mainFileName;
|
||||
}
|
||||
|
||||
public void prepareSource(HeliumPackage pkg, String[] moduleNameVersion,
|
||||
private void prepareSource(HeliumPackage pkg, String[] moduleNameVersion,
|
||||
String mainFileName) throws IOException {
|
||||
StringBuilder loadJsImport = new StringBuilder();
|
||||
StringBuilder loadJsRegister = new StringBuilder();
|
||||
|
|
@ -354,7 +351,7 @@ public class HeliumBundleFactory {
|
|||
loadJsImport.append(loadJsRegister).toString());
|
||||
}
|
||||
|
||||
public synchronized void installNodeModules(FrontendPluginFactory fpf) throws IOException {
|
||||
private synchronized void installNodeModules(FrontendPluginFactory fpf) throws IOException {
|
||||
try {
|
||||
out.reset();
|
||||
String commandForNpmInstall =
|
||||
|
|
@ -369,7 +366,7 @@ public class HeliumBundleFactory {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized File bundleHeliumPackage(FrontendPluginFactory fpf,
|
||||
private synchronized File bundleHeliumPackage(FrontendPluginFactory fpf,
|
||||
File bundleDir) throws IOException {
|
||||
try {
|
||||
out.reset();
|
||||
|
|
@ -404,10 +401,6 @@ public class HeliumBundleFactory {
|
|||
}
|
||||
|
||||
String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
|
||||
if (moduleNameVersion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (moduleNameVersion == null) {
|
||||
logger.error("Can't get module name and version of package " + pkg.getName());
|
||||
return null;
|
||||
|
|
@ -466,7 +459,7 @@ public class HeliumBundleFactory {
|
|||
return bundleCache;
|
||||
}
|
||||
|
||||
public synchronized void buildAllPackages(List<HeliumPackage> pkgs, boolean rebuild)
|
||||
private synchronized void buildAllPackages(List<HeliumPackage> pkgs, boolean rebuild)
|
||||
throws IOException {
|
||||
|
||||
if (pkgs == null || pkgs.size() == 0) {
|
||||
|
|
@ -485,7 +478,7 @@ public class HeliumBundleFactory {
|
|||
}
|
||||
}
|
||||
|
||||
void copyFrameworkModule(boolean recopy, FileFilter filter,
|
||||
private void copyFrameworkModule(boolean recopy, FileFilter filter,
|
||||
File src, File dest) throws IOException {
|
||||
if (src != null) {
|
||||
if (recopy && dest.exists()) {
|
||||
|
|
@ -501,7 +494,7 @@ public class HeliumBundleFactory {
|
|||
}
|
||||
}
|
||||
|
||||
void deleteYarnCache() {
|
||||
private void deleteYarnCache() {
|
||||
FilenameFilter filter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
|
|
@ -664,7 +657,7 @@ public class HeliumBundleFactory {
|
|||
|
||||
private void npmCommand(String args, Map<String, String> env) throws TaskRunnerException {
|
||||
NpmRunner npm = frontEndPluginFactory.getNpmRunner(
|
||||
getProxyConfig(isSecure(defaultNpmRegistryUrl)), defaultNpmRegistryUrl);
|
||||
getProxyConfig(isSecure(defaultNpmInstallerUrl)), defaultNpmInstallerUrl);
|
||||
npm.execute(args, env);
|
||||
}
|
||||
|
||||
|
|
@ -679,7 +672,7 @@ public class HeliumBundleFactory {
|
|||
private void yarnCommand(FrontendPluginFactory fpf,
|
||||
String args, Map<String, String> env) throws TaskRunnerException {
|
||||
YarnRunner yarn = fpf.getYarnRunner(
|
||||
getProxyConfig(isSecure(defaultNpmRegistryUrl)), defaultNpmRegistryUrl);
|
||||
getProxyConfig(isSecure(defaultNpmInstallerUrl)), defaultNpmInstallerUrl);
|
||||
yarn.execute(args, env);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,15 @@ import java.util.Properties;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.internal.StringMap;
|
||||
|
||||
import org.apache.zeppelin.dep.Dependency;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.zeppelin.dep.Dependency;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import static org.apache.zeppelin.notebook.utility.IdHashes.generateId;
|
||||
|
||||
|
|
@ -413,27 +415,45 @@ public class InterpreterSetting {
|
|||
runtimeInfosToBeCleared = null;
|
||||
}
|
||||
|
||||
// For backward compatibility of interpreter.json format after ZEPPELIN-2654
|
||||
public void convertPermissionsFromUsersToOwners(JsonObject jsonObject) {
|
||||
if (jsonObject != null) {
|
||||
JsonObject option = jsonObject.getAsJsonObject("option");
|
||||
if (option != null) {
|
||||
JsonArray users = option.getAsJsonArray("users");
|
||||
if (users != null) {
|
||||
if (this.option.getOwners() == null) {
|
||||
this.option.owners = new LinkedList<>();
|
||||
}
|
||||
for (JsonElement user : users) {
|
||||
this.option.getOwners().add(user.getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For backward compatibility of interpreter.json format after ZEPPELIN-2403
|
||||
public void convertFlatPropertiesToPropertiesWithWidgets() {
|
||||
StringMap newProperties = new StringMap();
|
||||
if (properties != null && properties instanceof StringMap) {
|
||||
StringMap p = (StringMap) properties;
|
||||
if (properties != null && properties instanceof StringMap) {
|
||||
StringMap p = (StringMap) properties;
|
||||
|
||||
for (Object o : p.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) o;
|
||||
if (!(entry.getValue() instanceof StringMap)) {
|
||||
StringMap newProperty = new StringMap();
|
||||
newProperty.put("name", entry.getKey());
|
||||
newProperty.put("value", entry.getValue());
|
||||
newProperty.put("type", InterpreterPropertyType.TEXTAREA.getValue());
|
||||
newProperties.put(entry.getKey().toString(), newProperty);
|
||||
} else {
|
||||
// already converted
|
||||
return;
|
||||
for (Object o : p.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) o;
|
||||
if (!(entry.getValue() instanceof StringMap)) {
|
||||
StringMap newProperty = new StringMap();
|
||||
newProperty.put("name", entry.getKey());
|
||||
newProperty.put("value", entry.getValue());
|
||||
newProperty.put("type", InterpreterPropertyType.TEXTAREA.getValue());
|
||||
newProperties.put(entry.getKey().toString(), newProperty);
|
||||
} else {
|
||||
// already converted
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.properties = newProperties;
|
||||
}
|
||||
this.properties = newProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.apache.zeppelin.interpreter;
|
||||
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
|
@ -71,6 +74,8 @@ 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.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.internal.StringMap;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
|
|
@ -161,8 +166,9 @@ public class InterpreterSettingManager {
|
|||
InterpreterInfoSaving infoSaving;
|
||||
try (BufferedReader json =
|
||||
Files.newBufferedReader(interpreterBindingPath, StandardCharsets.UTF_8)) {
|
||||
infoSaving = gson.fromJson(json, InterpreterInfoSaving.class);
|
||||
|
||||
JsonParser jsonParser = new JsonParser();
|
||||
JsonObject jsonObject = jsonParser.parse(json).getAsJsonObject();
|
||||
infoSaving = gson.fromJson(jsonObject.toString(), InterpreterInfoSaving.class);
|
||||
for (String k : infoSaving.interpreterSettings.keySet()) {
|
||||
InterpreterSetting setting = infoSaving.interpreterSettings.get(k);
|
||||
|
||||
|
|
@ -185,13 +191,16 @@ public class InterpreterSettingManager {
|
|||
properties.put(key, new InterpreterProperty(key, fields.get("value"), type));
|
||||
}
|
||||
setting.setProperties(properties);
|
||||
|
||||
|
||||
// Always use separate interpreter process
|
||||
// While we decided to turn this feature on always (without providing
|
||||
// enable/disable option on GUI).
|
||||
// previously created setting should turn this feature on here.
|
||||
setting.getOption().setRemote(true);
|
||||
|
||||
setting.convertPermissionsFromUsersToOwners(
|
||||
jsonObject.getAsJsonObject("interpreterSettings").getAsJsonObject(setting.getId()));
|
||||
|
||||
// Update transient information from InterpreterSettingRef
|
||||
InterpreterSetting interpreterSettingObject =
|
||||
interpreterSettingsRef.get(setting.getGroup());
|
||||
|
|
|
|||
|
|
@ -342,17 +342,13 @@ public class Paragraph extends Job implements Serializable, Cloneable {
|
|||
return null;
|
||||
}
|
||||
|
||||
private boolean hasPermission(String user, List<String> intpUsers) {
|
||||
if (1 > intpUsers.size()) {
|
||||
private boolean hasPermission(List<String> userAndRoles, List<String> intpUsersAndRoles) {
|
||||
if (1 > intpUsersAndRoles.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String u : intpUsers) {
|
||||
if (user.trim().equals(u.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
Set<String> intersection = new HashSet<>(intpUsersAndRoles);
|
||||
intersection.retainAll(userAndRoles);
|
||||
return (intpUsersAndRoles.isEmpty() || (intersection.size() > 0));
|
||||
}
|
||||
|
||||
public boolean isBlankParagraph() {
|
||||
|
|
@ -441,12 +437,12 @@ public class Paragraph extends Job implements Serializable, Cloneable {
|
|||
}
|
||||
|
||||
private boolean interpreterHasUser(InterpreterSetting intp) {
|
||||
return intp.getOption().permissionIsSet() && intp.getOption().getUsers() != null;
|
||||
return intp.getOption().permissionIsSet() && intp.getOption().getOwners() != null;
|
||||
}
|
||||
|
||||
private boolean isUserAuthorizedToAccessInterpreter(InterpreterOption intpOpt) {
|
||||
return intpOpt.permissionIsSet() && hasPermission(authenticationInfo.getUser(),
|
||||
intpOpt.getUsers());
|
||||
return intpOpt.permissionIsSet() && hasPermission(authenticationInfo.getUsersAndRoles(),
|
||||
intpOpt.getOwners());
|
||||
}
|
||||
|
||||
private InterpreterSetting getInterpreterSettingById(String id) {
|
||||
|
|
|
|||
|
|
@ -27,13 +27,18 @@ import java.util.Properties;
|
|||
*/
|
||||
public class Util {
|
||||
private static final String PROJECT_PROPERTIES_VERSION_KEY = "version";
|
||||
private static final String GIT_PROPERTIES_COMMIT_ID_KEY = "git.commit.id.abbrev";
|
||||
private static final String GIT_PROPERTIES_COMMIT_TS_KEY = "git.commit.time";
|
||||
|
||||
private static Properties projectProperties;
|
||||
private static Properties gitProperties;
|
||||
|
||||
static {
|
||||
projectProperties = new Properties();
|
||||
gitProperties = new Properties();
|
||||
try {
|
||||
projectProperties.load(Util.class.getResourceAsStream("/project.properties"));
|
||||
gitProperties.load(Util.class.getResourceAsStream("/git.properties"));
|
||||
} catch (IOException e) {
|
||||
//Fail to read project.properties
|
||||
}
|
||||
|
|
@ -48,4 +53,24 @@ public class Util {
|
|||
return StringUtils.defaultIfEmpty(projectProperties.getProperty(PROJECT_PROPERTIES_VERSION_KEY),
|
||||
StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zeppelin Git latest commit id
|
||||
*
|
||||
* @return Latest Zeppelin commit id
|
||||
*/
|
||||
public static String getGitCommitId() {
|
||||
return StringUtils.defaultIfEmpty(gitProperties.getProperty(GIT_PROPERTIES_COMMIT_ID_KEY),
|
||||
StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zeppelin Git latest commit timestamp
|
||||
*
|
||||
* @return Latest Zeppelin commit timestamp
|
||||
*/
|
||||
public static String getGitTimestamp() {
|
||||
return StringUtils.defaultIfEmpty(gitProperties.getProperty(GIT_PROPERTIES_COMMIT_TS_KEY),
|
||||
StringUtils.EMPTY);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ public class HeliumBundleFactoryTest {
|
|||
assertEquals(lastModified, bundle.lastModified());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void bundleLocalPackage() throws IOException, TaskRunnerException {
|
||||
URL res = Resources.getResource("helium/webpack.config.js");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class UtilTest {
|
||||
|
||||
@Test
|
||||
public void getVersionTest() {
|
||||
assertNotNull(Util.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGitInfoTest() {
|
||||
assertNotNull(Util.getGitCommitId());
|
||||
assertNotNull(Util.getGitTimestamp());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue