mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2718
This commit is contained in:
commit
55b0f7b0ed
49 changed files with 1753 additions and 870 deletions
11
.travis.yml
11
.travis.yml
|
|
@ -43,6 +43,7 @@ matrix:
|
|||
include:
|
||||
# Test License compliance using RAT tool
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.0.2" HADOOP_VER="2.6" PROFILE="-Prat" BUILD_FLAG="clean" TEST_FLAG="org.apache.rat:apache-rat-plugin:check" TEST_PROJECTS=""
|
||||
|
||||
# Run e2e tests (in zeppelin-web)
|
||||
|
|
@ -66,43 +67,53 @@ matrix:
|
|||
# HeliumApplicationFactoryTest - https://issues.apache.org/jira/browse/ZEPPELIN-2470
|
||||
# After issues are fixed these tests need to be included back by removing them from the "-Dtests.to.exclude" property
|
||||
- jdk: "oraclejdk8"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.2.0" HADOOP_VER="2.6" PROFILE="-Pspark-2.2 -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"
|
||||
dist: precise
|
||||
env: TEST_SELENIUM="true" SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-1.6 -Phadoop-2.6 -Phelium-dev -Pexamples" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark -Dtest=org.apache.zeppelin.AbstractFunctionalSuite -DfailIfNoTests=false"
|
||||
|
||||
# Test interpreter modules
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
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.2.0 with scala 2.11, livy
|
||||
- jdk: "oraclejdk8"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.2.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-2.2 -Phadoop-2.6 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,livy" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.*,org.apache.zeppelin.livy.* -DfailIfNoTests=false"
|
||||
|
||||
# Test spark module for 2.1.0 with scala 2.11, livy
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.1.0" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-2.1 -Phadoop-2.6 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,livy" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.*,org.apache.zeppelin.livy.* -DfailIfNoTests=false"
|
||||
|
||||
# Test spark module for 2.0.2 with scala 2.11
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="2.0.2" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-2.0 -Phadoop-2.6 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false"
|
||||
|
||||
# Test spark module for 1.6.3 with scala 2.10
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.10" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-1.6 -Phadoop-2.6 -Pscala-2.10" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.*,org.apache.zeppelin.spark.* -DfailIfNoTests=false"
|
||||
|
||||
# Test spark module for 1.6.3 with scala 2.11
|
||||
- jdk: "oraclejdk7"
|
||||
dist: precise
|
||||
env: SCALA_VER="2.11" SPARK_VER="1.6.3" HADOOP_VER="2.6" PROFILE="-Pweb-ci -Pspark-1.6 -Phadoop-2.6 -Pscala-2.11" SPARKR="true" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="test -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark" TEST_PROJECTS="-Dtest=ZeppelinSparkClusterTest,org.apache.zeppelin.spark.* -DfailIfNoTests=false"
|
||||
|
||||
# Test python/pyspark with python 2, livy 0.2
|
||||
- sudo: required
|
||||
dist: precise
|
||||
jdk: "oraclejdk7"
|
||||
env: PYTHON="2" SCALA_VER="2.10" SPARK_VER="1.6.1" HADOOP_VER="2.6" LIVY_VER="0.2.0" PROFILE="-Pspark-1.6 -Phadoop-2.6 -Plivy-0.2" BUILD_FLAG="package -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-display,spark-dependencies,spark,python,livy" TEST_PROJECTS="-Dtest=LivySQLInterpreterTest,org.apache.zeppelin.spark.PySpark*Test,org.apache.zeppelin.python.* -Dpyspark.test.exclude='' -DfailIfNoTests=false"
|
||||
|
||||
# Test python/pyspark with python 3, livy 0.3
|
||||
- sudo: required
|
||||
dist: precise
|
||||
jdk: "oraclejdk7"
|
||||
env: PYTHON="3" SCALA_VER="2.11" SPARK_VER="2.0.0" HADOOP_VER="2.6" LIVY_VER="0.3.0" PROFILE="-Pspark-2.0 -Phadoop-2.6 -Pscala-2.11 -Plivy-0.3" BUILD_FLAG="package -am -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" MODULES="-pl .,zeppelin-interpreter,zeppelin-display,spark-dependencies,spark,python,livy" TEST_PROJECTS="-Dtest=LivySQLInterpreterTest,org.apache.zeppelin.spark.PySpark*Test,org.apache.zeppelin.python.* -Dpyspark.test.exclude='' -DfailIfNoTests=false"
|
||||
|
||||
|
|
|
|||
|
|
@ -202,9 +202,9 @@ fi
|
|||
|
||||
if [[ -n "${SPARK_SUBMIT}" ]]; then
|
||||
if [[ -n "$ZEPPELIN_IMPERSONATE_USER" ]] && [[ "$ZEPPELIN_IMPERSONATE_SPARK_PROXY_USER" != "false" ]]; then
|
||||
INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${ZEPPELIN_INTP_CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} --proxy-user ${ZEPPELIN_IMPERSONATE_USER} ${SPARK_APP_JAR} ${PORT}`
|
||||
INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${ZEPPELIN_INTP_CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} ${ZEPPELIN_SPARK_CONF} --proxy-user ${ZEPPELIN_IMPERSONATE_USER} ${SPARK_APP_JAR} ${PORT}`
|
||||
else
|
||||
INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${ZEPPELIN_INTP_CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} ${SPARK_APP_JAR} ${PORT}`
|
||||
INTERPRETER_RUN_COMMAND+=' '` echo ${SPARK_SUBMIT} --class ${ZEPPELIN_SERVER} --driver-class-path \"${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${ZEPPELIN_INTP_CLASSPATH}\" --driver-java-options \"${JAVA_INTP_OPTS}\" ${SPARK_SUBMIT_OPTIONS} ${ZEPPELIN_SPARK_CONF} ${SPARK_APP_JAR} ${PORT}`
|
||||
fi
|
||||
else
|
||||
INTERPRETER_RUN_COMMAND+=' '` echo ${ZEPPELIN_RUNNER} ${JAVA_INTP_OPTS} ${ZEPPELIN_INTP_MEM} -cp ${ZEPPELIN_INTP_CLASSPATH_OVERRIDES}:${ZEPPELIN_INTP_CLASSPATH} ${ZEPPELIN_SERVER} ${PORT} `
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ ldapRealm.contextFactory.authenticationMechanism = simple
|
|||
|
||||
The other more flexible option is to use the LdapRealm. It allows for mapping of ldapgroups to roles and also allows for
|
||||
role/group based authentication into the zeppelin server. Sample configuration for this realm is given below.
|
||||
```
|
||||
|
||||
```
|
||||
[main]
|
||||
ldapRealm=org.apache.zeppelin.realm.LdapRealm
|
||||
|
||||
|
|
@ -179,7 +180,7 @@ ldapRealm.allowedRolesForAuthentication = admin_role,user_role
|
|||
ldapRealm.permissionsByRole= user_role = *:ToDoItemsJdo:*:*, *:ToDoItem:*:*; admin_role = *
|
||||
securityManager.sessionManager = $sessionManager
|
||||
securityManager.realms = $ldapRealm
|
||||
```
|
||||
```
|
||||
|
||||
### PAM
|
||||
[PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) authentication support allows the reuse of existing authentication
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
|
||||
## Helium REST API List
|
||||
|
||||
### List of all available helium packages
|
||||
### Get all available helium packages
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
|
|
@ -42,7 +42,7 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/all```</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/package```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
|
|
@ -78,43 +78,61 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
},
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"zeppelin-bubblechart": [
|
||||
]
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Get all enabled helium packages
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns all enabled helium packages in configured registries.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/enabledPackage```</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": "",
|
||||
"body": {
|
||||
"zeppelin.clock": [
|
||||
{
|
||||
"registry": "local",
|
||||
"pkg": {
|
||||
"type": "VISUALIZATION",
|
||||
"name": "zeppelin-bubblechart",
|
||||
"description": "Animated bubble chart",
|
||||
"artifact": ".\/..\/helium\/zeppelin-bubble",
|
||||
"icon": "icon"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"registry": "local",
|
||||
"pkg": {
|
||||
"type": "VISUALIZATION",
|
||||
"name": "zeppelin-bubblechart",
|
||||
"description": "Animated bubble chart",
|
||||
"artifact": "zeppelin-bubblechart@0.0.2",
|
||||
"type": "APPLICATION",
|
||||
"name": "zeppelin.clock",
|
||||
"description": "Clock (example)",
|
||||
"artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
|
||||
"className": "org.apache.zeppelin.example.app.clock.Clock",
|
||||
"resources": [
|
||||
[
|
||||
":java.util.Date"
|
||||
]
|
||||
],
|
||||
"icon": "icon"
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"zeppelin\_horizontalbar": [
|
||||
{
|
||||
"registry": "local",
|
||||
"pkg": {
|
||||
"type": "VISUALIZATION",
|
||||
"name": "zeppelin_horizontalbar",
|
||||
"description": "Horizontal Bar chart (example)",
|
||||
"artifact": ".\/zeppelin-examples\/zeppelin-example-horizontalbar",
|
||||
"icon": "icon"
|
||||
},
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -122,15 +140,70 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
### Suggest Helium application
|
||||
|
||||
### Get single helium package
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns suggested helium application for the paragraph.</td>
|
||||
<td>This ```GET``` method returns specified helium package information</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/package/[Package Name]```</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": "",
|
||||
"body": {
|
||||
"zeppelin.clock": [
|
||||
{
|
||||
"registry": "local",
|
||||
"pkg": {
|
||||
"type": "APPLICATION",
|
||||
"name": "zeppelin.clock",
|
||||
"description": "Clock (example)",
|
||||
"artifact": "zeppelin-examples\/zeppelin-example-clock\/target\/zeppelin-example-clock-0.7.0-SNAPSHOT.jar",
|
||||
"className": "org.apache.zeppelin.example.app.clock.Clock",
|
||||
"resources": [
|
||||
[
|
||||
":java.util.Date"
|
||||
]
|
||||
],
|
||||
"icon": "icon"
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Suggest Helium package on a paragraph
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns suggested helium package for the paragraph.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
|
|
@ -180,15 +253,15 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
### Load helium Application on a paragraph
|
||||
|
||||
### Load Helium package on a paragraph
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns a helium Application id on success.</td>
|
||||
<td>This ```POST``` method loads helium package to target paragraph.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
|
|
@ -201,8 +274,8 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
<tr>
|
||||
<td>Fail code</td>
|
||||
<td>
|
||||
404 on note or paragraph not exists <br/>
|
||||
500 for any other errors
|
||||
404 on note or paragraph not exists <br />
|
||||
500
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -218,19 +291,19 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
### Load bundled visualization script
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns bundled helium visualization javascript. When refresh=true (optional) is provided, Zeppelin rebuild bundle. otherwise, provided from cache</td>
|
||||
<td>This ```GET``` method returns bundled helium visualization javascript. When refresh=true (optional) is provided, Zeppelin rebuilds bundle. Otherwise, it's provided from cache</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizations/load[?refresh=true]```</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/bundle/load/[Package Name][?refresh=true]```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
|
|
@ -243,8 +316,8 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
### Enable package
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
|
|
@ -281,8 +354,8 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
### Disable package
|
||||
|
||||
<table class="table-configuration">
|
||||
|
|
@ -310,7 +383,7 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<br/>
|
||||
|
||||
### Get visualization display order
|
||||
|
||||
|
|
@ -322,7 +395,7 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/order/visualization```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
|
|
@ -339,9 +412,7 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<br />
|
||||
<br/>
|
||||
|
||||
### Set visualization display order
|
||||
|
||||
|
|
@ -353,7 +424,7 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/visualizationOrder```</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/order/visualization```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
|
|
@ -375,4 +446,98 @@ zeppelin-examples/zeppelin-example-clock/target/zeppelin-example-clock-0.7.0-SNA
|
|||
<code>{"status":"OK"}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Get configuration for all Helium packages
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns configuration for all Helium packages</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/config```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 500 </td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Get configuration for specific package
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns configuration for the specified package name and artifact</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/config/[Package Name]/[Artifact]```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 500 </td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Set configuration for specific package
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```POST``` method updates configuration for specified package name and artifact</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/config/[Package Name]/[Artifact]```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 500 </td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
### Get Spell configuration for single package
|
||||
|
||||
<table class="table-configuration">
|
||||
<col width="200">
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>This ```GET``` method returns specified package Spell configuration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>```http://[zeppelin-server]:[zeppelin-port]/api/helium/spell/config/[Package Name]```</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Success code</td>
|
||||
<td>200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Fail code</td>
|
||||
<td> 500 </td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
|
@ -29,7 +29,8 @@
|
|||
"envName": null,
|
||||
"propertyName": "default.completer.ttlInSeconds",
|
||||
"defaultValue": "120",
|
||||
"description": "Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)"
|
||||
"description": "Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)",
|
||||
"type": "number"
|
||||
},
|
||||
"default.driver": {
|
||||
"envName": null,
|
||||
|
|
@ -55,8 +56,9 @@
|
|||
"default.splitQueries": {
|
||||
"envName": null,
|
||||
"propertyName": "default.splitQueries",
|
||||
"defaultValue": "false",
|
||||
"description": "Each query is executed apart and returns the result"
|
||||
"defaultValue": false,
|
||||
"description": "Each query is executed apart and returns the result",
|
||||
"type": "checkbox"
|
||||
},
|
||||
"common.max_count": {
|
||||
"envName": null,
|
||||
|
|
|
|||
|
|
@ -359,10 +359,10 @@ public class SparkInterpreter extends Interpreter {
|
|||
* Create SparkSession
|
||||
*/
|
||||
public Object createSparkSession() {
|
||||
logger.info("------ Create new SparkContext {} -------", getProperty("master"));
|
||||
// use local mode for embedded spark mode when spark.master is not found
|
||||
conf.setIfMissing("spark.master", "local");
|
||||
logger.info("------ Create new SparkSession {} -------", conf.get("spark.master"));
|
||||
String execUri = System.getenv("SPARK_EXECUTOR_URI");
|
||||
conf.setAppName(getProperty("spark.app.name"));
|
||||
|
||||
if (outputDir != null) {
|
||||
conf.set("spark.repl.class.outputDir", outputDir.getAbsolutePath());
|
||||
}
|
||||
|
|
@ -370,11 +370,6 @@ public class SparkInterpreter extends Interpreter {
|
|||
if (execUri != null) {
|
||||
conf.set("spark.executor.uri", execUri);
|
||||
}
|
||||
|
||||
if (System.getenv("SPARK_HOME") != null) {
|
||||
conf.setSparkHome(System.getenv("SPARK_HOME"));
|
||||
}
|
||||
|
||||
conf.set("spark.scheduler.mode", "FAIR");
|
||||
conf.setMaster(getProperty("master"));
|
||||
if (isYarnMode()) {
|
||||
|
|
@ -383,7 +378,6 @@ public class SparkInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
Properties intpProperty = getProperty();
|
||||
|
||||
for (Object k : intpProperty.keySet()) {
|
||||
String key = (String) k;
|
||||
String val = toString(intpProperty.get(key));
|
||||
|
|
@ -436,7 +430,11 @@ public class SparkInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
public SparkContext createSparkContext_1() {
|
||||
logger.info("------ Create new SparkContext {} -------", getProperty("master"));
|
||||
// use local mode for embedded spark mode when spark.master is not found
|
||||
if (!conf.contains("spark.master")) {
|
||||
conf.setMaster("local");
|
||||
}
|
||||
logger.info("------ Create new SparkContext {} -------", conf.get("spark.master"));
|
||||
|
||||
String execUri = System.getenv("SPARK_EXECUTOR_URI");
|
||||
String[] jars = null;
|
||||
|
|
@ -490,9 +488,6 @@ public class SparkInterpreter extends Interpreter {
|
|||
classServerUri = (String) Utils.invokeMethod(classServer, "uri");
|
||||
}
|
||||
|
||||
conf.setMaster(getProperty("master"))
|
||||
.setAppName(getProperty("spark.app.name"));
|
||||
|
||||
if (classServerUri != null) {
|
||||
conf.set("spark.repl.class.uri", classServerUri);
|
||||
}
|
||||
|
|
@ -508,13 +503,9 @@ public class SparkInterpreter extends Interpreter {
|
|||
if (execUri != null) {
|
||||
conf.set("spark.executor.uri", execUri);
|
||||
}
|
||||
if (System.getenv("SPARK_HOME") != null) {
|
||||
conf.setSparkHome(System.getenv("SPARK_HOME"));
|
||||
}
|
||||
conf.set("spark.scheduler.mode", "FAIR");
|
||||
|
||||
Properties intpProperty = getProperty();
|
||||
|
||||
for (Object k : intpProperty.keySet()) {
|
||||
String key = (String) k;
|
||||
String val = toString(intpProperty.get(key));
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.apache.zeppelin.helium;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -26,8 +25,6 @@ import static org.junit.Assert.*;
|
|||
|
||||
public class HeliumPackageTest {
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
@Test
|
||||
public void parseSpellPackageInfo() {
|
||||
String examplePackage = "{\n" +
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.io.Reader;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -100,8 +101,9 @@ public class JupyterUtil {
|
|||
name = "Note converted from Jupyter";
|
||||
}
|
||||
note.setName(name);
|
||||
|
||||
|
||||
String lineSeparator = System.lineSeparator();
|
||||
String emptyString = "";
|
||||
Paragraph paragraph;
|
||||
List<Paragraph> paragraphs = new ArrayList<>();
|
||||
String interpreterName;
|
||||
|
|
@ -119,7 +121,8 @@ public class JupyterUtil {
|
|||
TypeData typeData;
|
||||
if (output instanceof Stream) {
|
||||
type = TypeData.TEXT;
|
||||
result = Joiner.on(lineSeparator).join(((Stream) output).getText());
|
||||
List<String> text = verifyEndOfLine(((Stream) output).getText(), lineSeparator);
|
||||
result = Joiner.on(emptyString).join(text);
|
||||
typeData = new TypeData(type, result);
|
||||
typeDataList.add(typeData);
|
||||
} else if (output instanceof ExecuteResult || output instanceof DisplayData) {
|
||||
|
|
@ -129,7 +132,8 @@ public class JupyterUtil {
|
|||
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());
|
||||
List<String> text = verifyEndOfLine((List<String>) datum.getValue(), lineSeparator);
|
||||
result = Joiner.on(emptyString).join(text);
|
||||
} else if (IMAGE_PNG.equals(datum.getKey())) {
|
||||
type = TypeData.HTML;
|
||||
result = makeHTML(((String) datum.getValue()).replace("\n", ""));
|
||||
|
|
@ -144,8 +148,9 @@ public class JupyterUtil {
|
|||
// Error
|
||||
Error error = (Error) output;
|
||||
type = TypeData.TEXT;
|
||||
result =
|
||||
Joiner.on(lineSeparator).join(new String[] {error.getEname(), error.getEvalue()});
|
||||
List<String> text = verifyEndOfLine(Arrays.asList(error.getEname(), error.getEvalue()),
|
||||
lineSeparator);
|
||||
result = Joiner.on(emptyString).join(text);
|
||||
typeData = new TypeData(type, result);
|
||||
typeDataList.add(typeData);
|
||||
}
|
||||
|
|
@ -156,8 +161,8 @@ public class JupyterUtil {
|
|||
interpreterName = "";
|
||||
}
|
||||
|
||||
paragraph.setText(
|
||||
interpreterName + lineSeparator + Joiner.on(lineSeparator).join(cell.getSource()));
|
||||
List<String> source = verifyEndOfLine(cell.getSource(), lineSeparator);
|
||||
paragraph.setText(interpreterName + lineSeparator + Joiner.on(emptyString).join(source));
|
||||
paragraph.setResults(new Result(Result.SUCCESS, typeDataList));
|
||||
|
||||
paragraphs.add(paragraph);
|
||||
|
|
@ -168,6 +173,21 @@ public class JupyterUtil {
|
|||
return note;
|
||||
}
|
||||
|
||||
private List<String> verifyEndOfLine(List<String> content, String lineSeparator) {
|
||||
if (null == content || content.size() == 1) {
|
||||
// one-liners don't have line separator
|
||||
return content;
|
||||
}
|
||||
for (int i = 0; i < content.size(); i++) {
|
||||
String line = content.get(i);
|
||||
// verify to end with line separator except the last element
|
||||
if (null != line && !line.endsWith(lineSeparator) && i != (content.size() - 1)) {
|
||||
content.set(i, line + lineSeparator);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private Gson getGson(GsonBuilder gsonBuilder) {
|
||||
return gsonBuilder.registerTypeAdapterFactory(cellTypeFactory)
|
||||
.registerTypeAdapterFactory(outputTypeFactory).create();
|
||||
|
|
|
|||
|
|
@ -51,32 +51,37 @@ public class HeliumRestApi {
|
|||
private Notebook notebook;
|
||||
private Gson gson = new Gson();
|
||||
|
||||
public HeliumRestApi() {
|
||||
}
|
||||
|
||||
public HeliumRestApi(Helium helium, Notebook notebook) {
|
||||
this.helium = helium;
|
||||
this.notebook = notebook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all package infos
|
||||
* Get all packages info
|
||||
*/
|
||||
@GET
|
||||
@Path("package")
|
||||
public Response getAllPackageInfo() {
|
||||
return new JsonResponse(
|
||||
Response.Status.OK, "", helium.getAllPackageInfo()).build();
|
||||
try {
|
||||
return new JsonResponse<>(Response.Status.OK, "", helium.getAllPackageInfo()).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled package infos
|
||||
* Get all enabled packages info
|
||||
*/
|
||||
@GET
|
||||
@Path("enabledPackage")
|
||||
public Response getAllEnabledPackageInfo() {
|
||||
return new JsonResponse(
|
||||
Response.Status.OK, "", helium.getAllEnabledPackages()).build();
|
||||
try {
|
||||
return new JsonResponse<>(Response.Status.OK, "", helium.getAllEnabledPackages()).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,7 +97,7 @@ public class HeliumRestApi {
|
|||
}
|
||||
|
||||
try {
|
||||
return new JsonResponse(
|
||||
return new JsonResponse<>(
|
||||
Response.Status.OK, "", helium.getSinglePackageInfo(packageName)).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
|
|
@ -114,13 +119,19 @@ public class HeliumRestApi {
|
|||
return new JsonResponse(Response.Status.NOT_FOUND, "Paragraph " + paragraphId + " not found")
|
||||
.build();
|
||||
}
|
||||
try {
|
||||
return new JsonResponse<>(Response.Status.OK, "", helium.suggestApp(paragraph)).build();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
|
||||
return new JsonResponse(Response.Status.OK, "", helium.suggestApp(paragraph)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("load/{noteId}/{paragraphId}")
|
||||
public Response suggest(@PathParam("noteId") String noteId,
|
||||
public Response load(@PathParam("noteId") String noteId,
|
||||
@PathParam("paragraphId") String paragraphId,
|
||||
String heliumPackage) {
|
||||
|
||||
|
|
@ -135,9 +146,14 @@ public class HeliumRestApi {
|
|||
.build();
|
||||
}
|
||||
HeliumPackage pkg = HeliumPackage.fromJson(heliumPackage);
|
||||
|
||||
String appId = helium.getApplicationFactory().loadAndRun(pkg, paragraph);
|
||||
return new JsonResponse(Response.Status.OK, "", appId).build();
|
||||
try {
|
||||
return new JsonResponse<>(Response.Status.OK, "",
|
||||
helium.getApplicationFactory().loadAndRun(pkg, paragraph)).build();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
|
|
@ -190,8 +206,12 @@ public class HeliumRestApi {
|
|||
public Response enablePackage(@PathParam("packageName") String packageName,
|
||||
String artifact) {
|
||||
try {
|
||||
helium.enable(packageName, artifact);
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
if (helium.enable(packageName, artifact)) {
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
}
|
||||
else {
|
||||
return new JsonResponse(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
|
|
@ -202,8 +222,12 @@ public class HeliumRestApi {
|
|||
@Path("disable/{packageName}")
|
||||
public Response disablePackage(@PathParam("packageName") String packageName) {
|
||||
try {
|
||||
helium.disable(packageName);
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
if (helium.disable(packageName)) {
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
}
|
||||
else {
|
||||
return new JsonResponse(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
|
|
@ -227,7 +251,7 @@ public class HeliumRestApi {
|
|||
"Failed to find enabled package for " + packageName).build();
|
||||
}
|
||||
|
||||
return new JsonResponse(Response.Status.OK, config).build();
|
||||
return new JsonResponse<>(Response.Status.OK, config).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
|
|
@ -239,7 +263,7 @@ public class HeliumRestApi {
|
|||
public Response getAllPackageConfigs() {
|
||||
try {
|
||||
Map<String, Map<String, Object>> config = helium.getAllPackageConfig();
|
||||
return new JsonResponse(Response.Status.OK, config).build();
|
||||
return new JsonResponse<>(Response.Status.OK, config).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
|
|
@ -265,7 +289,7 @@ public class HeliumRestApi {
|
|||
"Failed to find package for " + artifact).build();
|
||||
}
|
||||
|
||||
return new JsonResponse(Response.Status.OK, config).build();
|
||||
return new JsonResponse<>(Response.Status.OK, config).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
|
|
@ -284,12 +308,11 @@ public class HeliumRestApi {
|
|||
).build();
|
||||
}
|
||||
|
||||
Map<String, Object> packageConfig = null;
|
||||
|
||||
try {
|
||||
packageConfig = gson.fromJson(
|
||||
Map<String, Object> packageConfig = gson.fromJson(
|
||||
rawConfig, new TypeToken<Map<String, Object>>(){}.getType());
|
||||
helium.updatePackageConfig(artifact, packageConfig);
|
||||
return new JsonResponse<>(Response.Status.OK, packageConfig).build();
|
||||
} catch (JsonParseException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.BAD_REQUEST,
|
||||
|
|
@ -298,15 +321,18 @@ public class HeliumRestApi {
|
|||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR,
|
||||
e.getMessage()).build();
|
||||
}
|
||||
|
||||
return new JsonResponse(Response.Status.OK, packageConfig).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("order/visualization")
|
||||
public Response getVisualizationPackageOrder() {
|
||||
List<String> order = helium.getVisualizationPackageOrder();
|
||||
return new JsonResponse(Response.Status.OK, order).build();
|
||||
try {
|
||||
List<String> order = helium.getVisualizationPackageOrder();
|
||||
return new JsonResponse<>(Response.Status.OK, order).build();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
|
|
@ -314,13 +340,12 @@ public class HeliumRestApi {
|
|||
public Response setVisualizationPackageOrder(String orderedPackageNameList) {
|
||||
List<String> orderedList = gson.fromJson(
|
||||
orderedPackageNameList, new TypeToken<List<String>>(){}.getType());
|
||||
|
||||
try {
|
||||
helium.setVisualizationPackageOrder(orderedList);
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new JsonResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()).build();
|
||||
}
|
||||
return new JsonResponse(Response.Status.OK).build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,40 +473,45 @@ public class NotebookServer extends WebSocketServlet
|
|||
}
|
||||
|
||||
private void broadcast(String noteId, Message m) {
|
||||
List<NotebookSocket> socketsToBroadcast = Collections.emptyList();
|
||||
synchronized (noteSocketMap) {
|
||||
broadcastToWatchers(noteId, StringUtils.EMPTY, m);
|
||||
List<NotebookSocket> socketLists = noteSocketMap.get(noteId);
|
||||
if (socketLists == null || socketLists.size() == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.debug("SEND >> " + m);
|
||||
for (NotebookSocket conn : socketLists) {
|
||||
try {
|
||||
conn.send(serializeMessage(m));
|
||||
} catch (IOException e) {
|
||||
LOG.error("socket error", e);
|
||||
}
|
||||
socketsToBroadcast = new ArrayList<>(socketLists);
|
||||
}
|
||||
LOG.debug("SEND >> " + m);
|
||||
for (NotebookSocket conn : socketsToBroadcast) {
|
||||
try {
|
||||
conn.send(serializeMessage(m));
|
||||
} catch (IOException e) {
|
||||
LOG.error("socket error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastExcept(String noteId, Message m, NotebookSocket exclude) {
|
||||
List<NotebookSocket> socketsToBroadcast = Collections.emptyList();
|
||||
synchronized (noteSocketMap) {
|
||||
broadcastToWatchers(noteId, StringUtils.EMPTY, m);
|
||||
List<NotebookSocket> socketLists = noteSocketMap.get(noteId);
|
||||
if (socketLists == null || socketLists.size() == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.debug("SEND >> " + m);
|
||||
for (NotebookSocket conn : socketLists) {
|
||||
if (exclude.equals(conn)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
conn.send(serializeMessage(m));
|
||||
} catch (IOException e) {
|
||||
LOG.error("socket error", e);
|
||||
}
|
||||
socketsToBroadcast = new ArrayList<>(socketLists);
|
||||
}
|
||||
|
||||
LOG.debug("SEND >> " + m);
|
||||
for (NotebookSocket conn : socketsToBroadcast) {
|
||||
if (exclude.equals(conn)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
conn.send(serializeMessage(m));
|
||||
} catch (IOException e) {
|
||||
LOG.error("socket error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.zeppelin.rest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.internal.StringMap;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
import org.apache.commons.httpclient.methods.PostMethod;
|
||||
import org.apache.zeppelin.helium.*;
|
||||
import org.apache.zeppelin.server.ZeppelinServer;
|
||||
import org.junit.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HeliumRestApiTest extends AbstractTestRestApi {
|
||||
Gson gson = new Gson();
|
||||
|
||||
@BeforeClass
|
||||
public static void init() throws Exception {
|
||||
AbstractTestRestApi.startUp();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void destroy() throws Exception {
|
||||
AbstractTestRestApi.shutDown();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
HeliumTestRegistry registry = new HeliumTestRegistry("r1", "r1");
|
||||
ZeppelinServer.helium.clear();
|
||||
ZeppelinServer.helium.addRegistry(registry);
|
||||
|
||||
registry.add(new HeliumPackage(
|
||||
HeliumType.APPLICATION,
|
||||
"name1",
|
||||
"desc1",
|
||||
"artifact1",
|
||||
"className1",
|
||||
new String[][]{},
|
||||
"",
|
||||
""));
|
||||
|
||||
registry.add(new HeliumPackage(
|
||||
HeliumType.APPLICATION,
|
||||
"name2",
|
||||
"desc2",
|
||||
"artifact2",
|
||||
"className2",
|
||||
new String[][]{},
|
||||
"",
|
||||
""));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
ZeppelinServer.helium.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllPackageInfo() throws IOException {
|
||||
GetMethod get = httpGet("/helium/package");
|
||||
assertThat(get, isAllowed());
|
||||
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
Map<String, Set<String>> body = (Map<String, Set<String>>) resp.get("body");
|
||||
|
||||
assertEquals(body.size(), 2);
|
||||
assertTrue(body.containsKey("name1"));
|
||||
assertTrue(body.containsKey("name2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllEnabledPackageInfo() throws IOException {
|
||||
// No enabled packages initially
|
||||
GetMethod get1 = httpGet("/helium/enabledPackage");
|
||||
assertThat(get1, isAllowed());
|
||||
Map<String, Object> resp1 = gson.fromJson(get1.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<StringMap<Object>> body1 = (List<StringMap<Object>>) resp1.get("body");
|
||||
assertEquals(body1.size(), 0);
|
||||
|
||||
// Enable "name1" package
|
||||
ZeppelinServer.helium.enable("name1","artifact1");
|
||||
|
||||
GetMethod get2 = httpGet("/helium/enabledPackage");
|
||||
assertThat(get2, isAllowed());
|
||||
Map<String, Object> resp2 = gson.fromJson(get2.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<StringMap<Object>> body2 = (List<StringMap<Object>>) resp2.get("body");
|
||||
|
||||
assertEquals(body2.size(), 1);
|
||||
StringMap<Object> pkg = (StringMap<Object>) body2.get(0).get("pkg");
|
||||
assertEquals(pkg.get("name"), "name1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSinglePackageInfo() throws IOException {
|
||||
String packageName = "name1";
|
||||
GetMethod get = httpGet("/helium/package/" + packageName);
|
||||
assertThat(get, isAllowed());
|
||||
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<StringMap<Object>> body = (List<StringMap<Object>>) resp.get("body");
|
||||
|
||||
assertEquals(body.size(), 1);
|
||||
StringMap<Object> pkg = (StringMap<Object>) body.get(0).get("pkg");
|
||||
assertEquals(pkg.get("name"), "name1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllPackageConfigs() throws IOException {
|
||||
GetMethod get = httpGet("/helium/config/");
|
||||
assertThat(get, isAllowed());
|
||||
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
StringMap<Object> body = (StringMap<Object>) resp.get("body");
|
||||
// ToDo: Apply config with POST command and check update
|
||||
assertEquals(body.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPackageConfig() throws IOException {
|
||||
String packageName = "name1";
|
||||
String artifact = "artifact1";
|
||||
GetMethod get = httpGet("/helium/config/" + packageName + "/" + artifact);
|
||||
assertThat(get, isAllowed());
|
||||
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
StringMap<Object> body = (StringMap<Object>) resp.get("body");
|
||||
assertTrue(body.containsKey("confPersisted"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnableDisablePackage() throws IOException {
|
||||
String packageName = "name1";
|
||||
PostMethod post1 = httpPost("/helium/enable/" + packageName, "");
|
||||
assertThat(post1, isAllowed());
|
||||
post1.releaseConnection();
|
||||
|
||||
GetMethod get1 = httpGet("/helium/package/" + packageName);
|
||||
Map<String, Object> resp1 = gson.fromJson(get1.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<StringMap<Object>> body1 = (List<StringMap<Object>>) resp1.get("body");
|
||||
assertEquals(body1.get(0).get("enabled"), true);
|
||||
|
||||
PostMethod post2 = httpPost("/helium/disable/" + packageName, "");
|
||||
assertThat(post2, isAllowed());
|
||||
post2.releaseConnection();
|
||||
|
||||
GetMethod get2 = httpGet("/helium/package/" + packageName);
|
||||
Map<String, Object> resp2 = gson.fromJson(get2.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<StringMap<Object>> body2 = (List<StringMap<Object>>) resp2.get("body");
|
||||
assertEquals(body2.get(0).get("enabled"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVisualizationPackageOrder() throws IOException {
|
||||
GetMethod get1 = httpGet("/helium/order/visualization");
|
||||
assertThat(get1, isAllowed());
|
||||
Map<String, Object> resp1 = gson.fromJson(get1.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<Object> body1 = (List<Object>) resp1.get("body");
|
||||
assertEquals(body1.size(), 0);
|
||||
|
||||
//We assume allPackages list has been refreshed before sorting
|
||||
ZeppelinServer.helium.getAllPackageInfo();
|
||||
|
||||
String postRequestJson = "[name2, name1]";
|
||||
PostMethod post = httpPost("/helium/order/visualization", postRequestJson);
|
||||
assertThat(post, isAllowed());
|
||||
post.releaseConnection();
|
||||
|
||||
GetMethod get2 = httpGet("/helium/order/visualization");
|
||||
assertThat(get2, isAllowed());
|
||||
Map<String, Object> resp2 = gson.fromJson(get2.getResponseBodyAsString(),
|
||||
new TypeToken<Map<String, Object>>() { }.getType());
|
||||
List<Object> body2 = (List<Object>) resp2.get("body");
|
||||
assertEquals(body2.size(), 2);
|
||||
assertEquals(body2.get(0), "name2");
|
||||
assertEquals(body2.get(1), "name1");
|
||||
}
|
||||
}
|
||||
|
||||
class HeliumTestRegistry extends HeliumRegistry {
|
||||
private List<HeliumPackage> infos = new LinkedList<>();
|
||||
|
||||
public HeliumTestRegistry(String name, String uri) {
|
||||
super(name, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeliumPackage> getAll() throws IOException {
|
||||
return infos;
|
||||
}
|
||||
|
||||
public void add(HeliumPackage info) {
|
||||
infos.add(info);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,11 +14,10 @@
|
|||
|
||||
angular.module('zeppelinWebApp').controller('ConfigurationCtrl', ConfigurationCtrl)
|
||||
|
||||
function ConfigurationCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
||||
function ConfigurationCtrl ($scope, $http, baseUrlSrv, ngToast) {
|
||||
'ngInject'
|
||||
|
||||
$scope.configrations = []
|
||||
$scope._ = _
|
||||
ngToast.dismiss()
|
||||
|
||||
let getConfigurations = function () {
|
||||
|
|
|
|||
|
|
@ -35,19 +35,21 @@ limitations under the License.
|
|||
<div class="col-md-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%">name</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:30%">name</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="key in configurations | sortByKey" >
|
||||
<td>{{key}}</td>
|
||||
<td>
|
||||
<div class="hiding_overflow">
|
||||
{{configurations[key]}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr ng-repeat="key in configurations | sortByKey" >
|
||||
<td>{{key}}</td>
|
||||
<td>
|
||||
<div class="hiding_overflow">
|
||||
{{configurations[key]}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
69
zeppelin-web/src/app/configuration/configuration.test.js
Normal file
69
zeppelin-web/src/app/configuration/configuration.test.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import template from './configuration.html'
|
||||
|
||||
describe('Controller: Configuration', function () {
|
||||
beforeEach(angular.mock.module('zeppelinWebApp'))
|
||||
|
||||
let baseUrlSrvMock = { getRestApiBase: () => '' }
|
||||
|
||||
let ctrl // controller instance
|
||||
let $scope
|
||||
let $compile
|
||||
let $controller // controller generator
|
||||
let $httpBackend
|
||||
let ngToast
|
||||
|
||||
beforeEach(inject((_$controller_, _$rootScope_, _$compile_, _$httpBackend_, _ngToast_) => {
|
||||
$scope = _$rootScope_.$new()
|
||||
$compile = _$compile_
|
||||
$controller = _$controller_
|
||||
$httpBackend = _$httpBackend_
|
||||
ngToast = _ngToast_
|
||||
}))
|
||||
|
||||
afterEach(function () {
|
||||
$httpBackend.verifyNoOutstandingExpectation()
|
||||
$httpBackend.verifyNoOutstandingRequest()
|
||||
})
|
||||
|
||||
it('should get configuration initially', () => {
|
||||
const conf = { 'conf1': 'value1' }
|
||||
ctrl = $controller('ConfigurationCtrl', { $scope: $scope, baseUrlSrv: baseUrlSrvMock, })
|
||||
expect(ctrl).toBeDefined()
|
||||
|
||||
$httpBackend
|
||||
.when('GET', '/configurations/all')
|
||||
.respond(200, { body: conf, })
|
||||
$httpBackend.expectGET('/configurations/all')
|
||||
$httpBackend.flush()
|
||||
|
||||
expect($scope.configurations).toEqual(conf) // scope is updated after $httpBackend.flush()
|
||||
})
|
||||
|
||||
it('should display ngToast when failed to get configuration properly', () => {
|
||||
ctrl = $controller('ConfigurationCtrl', { $scope: $scope, baseUrlSrv: baseUrlSrvMock, })
|
||||
spyOn(ngToast, 'danger')
|
||||
|
||||
$httpBackend.when('GET', '/configurations/all').respond(401, {})
|
||||
$httpBackend.expectGET('/configurations/all')
|
||||
$httpBackend.flush()
|
||||
|
||||
expect(ngToast.danger).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should render list of configurations as the sorted order', () => {
|
||||
$scope.configurations = {
|
||||
'zeppelin.server.port': '8080',
|
||||
'zeppelin.server.addr': '0.0.0.0',
|
||||
}
|
||||
const elem = $compile(template)($scope)
|
||||
$scope.$digest()
|
||||
const tbody = elem.find('tbody')
|
||||
const tds = tbody.find('td')
|
||||
|
||||
// should be sorted
|
||||
expect(tds[0].innerText.trim()).toBe('zeppelin.server.addr')
|
||||
expect(tds[1].innerText.trim()).toBe('0.0.0.0')
|
||||
expect(tds[2].innerText.trim()).toBe('zeppelin.server.port')
|
||||
expect(tds[3].innerText.trim()).toBe('8080')
|
||||
})
|
||||
})
|
||||
|
|
@ -12,49 +12,60 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
angular.module('zeppelinWebApp').controller('CredentialCtrl', CredentialCtrl)
|
||||
angular.module('zeppelinWebApp').controller('CredentialCtrl', CredentialController)
|
||||
|
||||
function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
||||
function CredentialController($scope, $http, baseUrlSrv, ngToast) {
|
||||
'ngInject'
|
||||
|
||||
$scope._ = _
|
||||
ngToast.dismiss()
|
||||
|
||||
$scope.credentialInfo = []
|
||||
$scope.showAddNewCredentialInfo = false
|
||||
$scope.availableInterpreters = []
|
||||
|
||||
$scope.entity = ''
|
||||
$scope.password = ''
|
||||
$scope.username = ''
|
||||
|
||||
$scope.hasCredential = () => {
|
||||
return Array.isArray($scope.credentialInfo) && $scope.credentialInfo.length
|
||||
}
|
||||
|
||||
let getCredentialInfo = function () {
|
||||
$http.get(baseUrlSrv.getRestApiBase() + '/credential')
|
||||
.success(function (data, status, headers, config) {
|
||||
$scope.credentialInfo = _.map(data.body.userCredentials, function (value, prop) {
|
||||
return {entity: prop, password: value.password, username: value.username}
|
||||
.success(function (data, status, headers, config) {
|
||||
$scope.credentialInfo.length = 0 // keep the ref while cleaning
|
||||
const returnedCredentials = data.body.userCredentials
|
||||
|
||||
for (let key in returnedCredentials) {
|
||||
const value = returnedCredentials[key]
|
||||
$scope.credentialInfo.push({
|
||||
entity: key,
|
||||
password: value.password,
|
||||
username: value.username,
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Success %o %o', status, $scope.credentialInfo)
|
||||
})
|
||||
console.log('Success %o %o', status, $scope.credentialInfo)
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
if (status === 401) {
|
||||
ngToast.danger({
|
||||
content: 'You don\'t have permission on this page',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
})
|
||||
setTimeout(function () {
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
if (status === 401) {
|
||||
showToast('You do not have permission on this page', 'danger')
|
||||
setTimeout(function () {
|
||||
window.location = baseUrlSrv.getBase()
|
||||
}, 3000)
|
||||
}
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.isValidCredential = function() {
|
||||
return $scope.entity.trim() !== '' && $scope.username.trim() !== ''
|
||||
}
|
||||
|
||||
$scope.addNewCredentialInfo = function () {
|
||||
if ($scope.entity && _.isEmpty($scope.entity.trim()) &&
|
||||
$scope.username && _.isEmpty($scope.username.trim())) {
|
||||
ngToast.danger({
|
||||
content: 'Username \\ Entity can not be empty.',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
})
|
||||
if (!$scope.isValidCredential()) {
|
||||
showToast('Username \\ Entity can not be empty.', 'danger')
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -65,25 +76,17 @@ function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
}
|
||||
|
||||
$http.put(baseUrlSrv.getRestApiBase() + '/credential', newCredential)
|
||||
.success(function (data, status, headers, config) {
|
||||
ngToast.success({
|
||||
content: 'Successfully saved credentials.',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
.success(function (data, status, headers, config) {
|
||||
showToast('Successfully saved credentials.', 'success')
|
||||
$scope.credentialInfo.push(newCredential)
|
||||
resetCredentialInfo()
|
||||
$scope.showAddNewCredentialInfo = false
|
||||
console.log('Success %o %o', status, data.message)
|
||||
})
|
||||
$scope.credentialInfo.push(newCredential)
|
||||
resetCredentialInfo()
|
||||
$scope.showAddNewCredentialInfo = false
|
||||
console.log('Success %o %o', status, data.message)
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
ngToast.danger({
|
||||
content: 'Error saving credentials',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
.error(function (data, status, headers, config) {
|
||||
showToast('Error saving credentials', 'danger')
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
}
|
||||
|
||||
let getAvailableInterpreters = function () {
|
||||
|
|
@ -100,7 +103,9 @@ function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
return false
|
||||
}
|
||||
})
|
||||
}).error(function (data, status, headers, config) {
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
showToast(data.message, 'danger')
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
}
|
||||
|
|
@ -125,35 +130,32 @@ function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
}
|
||||
|
||||
$scope.copyOriginCredentialsInfo = function () {
|
||||
ngToast.info({
|
||||
content: 'Since entity is a unique key, you can edit only username & password',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
})
|
||||
showToast('Since entity is a unique key, you can edit only username & password', 'info')
|
||||
}
|
||||
|
||||
$scope.updateCredentialInfo = function (form, data, entity) {
|
||||
let request = {
|
||||
if (!$scope.isValidCredential()) {
|
||||
showToast('Username \\ Entity can not be empty.', 'danger')
|
||||
return
|
||||
}
|
||||
|
||||
let credential = {
|
||||
entity: entity,
|
||||
username: data.username,
|
||||
password: data.password
|
||||
}
|
||||
|
||||
$http.put(baseUrlSrv.getRestApiBase() + '/credential/', request)
|
||||
.success(function (data, status, headers, config) {
|
||||
let index = _.findIndex($scope.credentialInfo, {'entity': entity})
|
||||
$scope.credentialInfo[index] = request
|
||||
return true
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
console.log('Error %o %o', status, data.message)
|
||||
ngToast.danger({
|
||||
content: 'We couldn\'t save the credential',
|
||||
verticalPosition: 'bottom',
|
||||
timeout: '3000'
|
||||
$http.put(baseUrlSrv.getRestApiBase() + '/credential/', credential)
|
||||
.success(function (data, status, headers, config) {
|
||||
const index = $scope.credentialInfo.findIndex(elem => elem.entity === entity)
|
||||
$scope.credentialInfo[index] = credential
|
||||
return true
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
showToast('We could not save the credential', 'danger')
|
||||
console.log('Error %o %o', status, data.message)
|
||||
form.$show()
|
||||
})
|
||||
form.$show()
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -167,19 +169,33 @@ function CredentialCtrl ($scope, $rootScope, $http, baseUrlSrv, ngToast) {
|
|||
callback: function (result) {
|
||||
if (result) {
|
||||
$http.delete(baseUrlSrv.getRestApiBase() + '/credential/' + entity)
|
||||
.success(function (data, status, headers, config) {
|
||||
let index = _.findIndex($scope.credentialInfo, {'entity': entity})
|
||||
$scope.credentialInfo.splice(index, 1)
|
||||
console.log('Success %o %o', status, data.message)
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
.success(function (data, status, headers, config) {
|
||||
const index = $scope.credentialInfo.findIndex(elem => elem.entity === entity)
|
||||
$scope.credentialInfo.splice(index, 1)
|
||||
console.log('Success %o %o', status, data.message)
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
showToast(data.message, 'danger')
|
||||
console.log('Error %o %o', status, data.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showToast(message, type) {
|
||||
const verticalPosition = 'bottom'
|
||||
const timeout = '3000'
|
||||
|
||||
if (type === 'success') {
|
||||
ngToast.success({ content: message, verticalPosition: verticalPosition, timeout: timeout, })
|
||||
} else if (type === 'info') {
|
||||
ngToast.info({ content: message, verticalPosition: verticalPosition, timeout: timeout, })
|
||||
} else {
|
||||
ngToast.danger({ content: message, verticalPosition: verticalPosition, timeout: timeout, })
|
||||
}
|
||||
}
|
||||
|
||||
let init = function () {
|
||||
getAvailableInterpreters()
|
||||
getCredentialInfo()
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ limitations under the License.
|
|||
|
||||
<div class="box width-full">
|
||||
<div class="row interpreter">
|
||||
<div ng-show="_.isEmpty(credentialInfo) || valueform.$hidden"
|
||||
<div ng-show="!hasCredential(credentialInfo) || valueform.$hidden"
|
||||
class="col-md-12 gray40-message">
|
||||
<em>Currently there is no credential information</em>
|
||||
</div>
|
||||
<div class="col-md-12" ng-show="!_.isEmpty(credentialInfo) || valueform.$visible">
|
||||
<div class="col-md-12" ng-show="hasCredential(credentialInfo) || valueform.$visible">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -99,52 +99,54 @@ limitations under the License.
|
|||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="credential in credentialInfo">
|
||||
<td>
|
||||
<span>
|
||||
{{credential.entity}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-textarea="credential.username" e-name="username" e-form="valueform"
|
||||
e-msd-elastic focus-if="credential.username.length == 0">
|
||||
{{credential.username}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-password="credential.password" e-name="password" e-form="valueform"
|
||||
e-msd-elastic focus-if="credential.password.length == 0">
|
||||
**********
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Edit credential info -->
|
||||
<span style="float:right" ng-show="!valueform.$visible">
|
||||
<button class="btn btn-default btn-xs"
|
||||
ng-click="valueform.$show();
|
||||
copyOriginCredentialsInfo();">
|
||||
<span class="fa fa-pencil"></span> edit</button>
|
||||
<button class="btn btn-default btn-xs"
|
||||
ng-click="removeCredentialInfo(credential.entity)">
|
||||
<span class="fa fa-trash"></span> remove</button>
|
||||
<tbody>
|
||||
<tr ng-repeat="credential in credentialInfo">
|
||||
<td>
|
||||
<span>
|
||||
{{credential.entity}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-textarea="credential.username" e-name="username" e-form="valueform"
|
||||
e-msd-elastic focus-if="credential.username.length == 0">
|
||||
{{credential.username}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-password="credential.password" e-name="password" e-form="valueform"
|
||||
e-msd-elastic focus-if="credential.password.length == 0">
|
||||
**********
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Edit credential info -->
|
||||
<span style="float:right" ng-show="!valueform.$visible">
|
||||
<button class="btn btn-default btn-xs"
|
||||
ng-click="valueform.$show();
|
||||
copyOriginCredentialsInfo();">
|
||||
<span class="fa fa-pencil"></span> edit</button>
|
||||
<button class="btn btn-default btn-xs"
|
||||
ng-click="removeCredentialInfo(credential.entity)">
|
||||
<span class="fa fa-trash"></span> remove</button>
|
||||
|
||||
</span>
|
||||
<span style="float:right" ng-show="valueform.$visible">
|
||||
<form editable-form name="valueform"
|
||||
onbeforesave="updateCredentialInfo(valueform, $data, credential.entity)"
|
||||
ng-show="valueform.$visible">
|
||||
<button type="submit" class="btn btn-primary btn-xs">
|
||||
<span class="fa fa-check"></span> save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-xs"
|
||||
ng-disabled="valueform.$waiting"
|
||||
ng-click="valueform.$cancel();">
|
||||
<span class="fa fa-remove"></span> cancel
|
||||
</button>
|
||||
</form>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</span>
|
||||
<span style="float:right" ng-show="valueform.$visible">
|
||||
<form editable-form name="valueform"
|
||||
onbeforesave="updateCredentialInfo(valueform, $data, credential.entity)"
|
||||
ng-show="valueform.$visible">
|
||||
<button type="submit" class="btn btn-primary btn-xs">
|
||||
<span class="fa fa-check"></span> save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-xs"
|
||||
ng-disabled="valueform.$waiting"
|
||||
ng-click="valueform.$cancel();">
|
||||
<span class="fa fa-remove"></span> cancel
|
||||
</button>
|
||||
</form>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
114
zeppelin-web/src/app/credential/credential.test.js
Normal file
114
zeppelin-web/src/app/credential/credential.test.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
|
||||
describe('Controller: Credential', function () {
|
||||
beforeEach(angular.mock.module('zeppelinWebApp'))
|
||||
|
||||
let baseUrlSrvMock = { getRestApiBase: () => '' }
|
||||
|
||||
let $scope
|
||||
let $controller // controller generator
|
||||
let $httpBackend
|
||||
|
||||
beforeEach(inject((_$controller_, _$rootScope_, _$compile_, _$httpBackend_, _ngToast_) => {
|
||||
$scope = _$rootScope_.$new()
|
||||
$controller = _$controller_
|
||||
$httpBackend = _$httpBackend_
|
||||
}))
|
||||
|
||||
const credentialResponse = { 'spark.testCredential': { username: 'user1', password: 'password1' }, }
|
||||
const interpreterResponse = [
|
||||
{ 'name': 'spark', 'group': 'spark', },
|
||||
{ 'name': 'md', 'group': 'md', },
|
||||
] // simplified
|
||||
|
||||
function setupInitialization(credentialRes, interpreterRes) {
|
||||
// requests should follow the exact order
|
||||
$httpBackend
|
||||
.when('GET', '/interpreter/setting')
|
||||
.respond(200, { body: interpreterRes, })
|
||||
$httpBackend.expectGET('/interpreter/setting')
|
||||
$httpBackend
|
||||
.when('GET', '/credential')
|
||||
.respond(200, { body: { userCredentials: credentialRes, } })
|
||||
$httpBackend.expectGET('/credential')
|
||||
|
||||
// should flush after calling this function
|
||||
}
|
||||
|
||||
it('should get available interpreters and credentials initially', () => {
|
||||
const ctrl = createController()
|
||||
expect(ctrl).toBeDefined()
|
||||
|
||||
setupInitialization(credentialResponse, interpreterResponse)
|
||||
$httpBackend.flush()
|
||||
|
||||
expect($scope.credentialInfo).toEqual(
|
||||
[{ entity: 'spark.testCredential', username: 'user1', password: 'password1'}]
|
||||
)
|
||||
expect($scope.availableInterpreters).toEqual(
|
||||
['spark.spark', 'md.md']
|
||||
)
|
||||
|
||||
$httpBackend.verifyNoOutstandingExpectation()
|
||||
$httpBackend.verifyNoOutstandingRequest()
|
||||
})
|
||||
|
||||
it('should toggle using toggleAddNewCredentialInfo', () => {
|
||||
createController()
|
||||
|
||||
expect($scope.showAddNewCredentialInfo).toBe(false)
|
||||
$scope.toggleAddNewCredentialInfo()
|
||||
expect($scope.showAddNewCredentialInfo).toBe(true)
|
||||
$scope.toggleAddNewCredentialInfo()
|
||||
expect($scope.showAddNewCredentialInfo).toBe(false)
|
||||
})
|
||||
|
||||
it('should check empty credentials using isInvalidCredential', () => {
|
||||
createController()
|
||||
|
||||
$scope.entity = ''
|
||||
$scope.username = ''
|
||||
expect($scope.isValidCredential()).toBe(false)
|
||||
|
||||
$scope.entity = 'spark1'
|
||||
$scope.username = ''
|
||||
expect($scope.isValidCredential()).toBe(false)
|
||||
|
||||
$scope.entity = ''
|
||||
$scope.username = 'user1'
|
||||
expect($scope.isValidCredential()).toBe(false)
|
||||
|
||||
$scope.entity = 'spark'
|
||||
$scope.username = 'user1'
|
||||
expect($scope.isValidCredential()).toBe(true)
|
||||
})
|
||||
|
||||
it('should be able to add credential via addNewCredentialInfo', () => {
|
||||
const ctrl = createController()
|
||||
expect(ctrl).toBeDefined()
|
||||
setupInitialization(credentialResponse, interpreterResponse)
|
||||
|
||||
// when
|
||||
const newCredential = { entity: 'spark.sql', username: 'user2', password: 'password2'}
|
||||
|
||||
$httpBackend
|
||||
.when('PUT', '/credential', newCredential)
|
||||
.respond(200, { })
|
||||
$httpBackend.expectPUT('/credential', newCredential)
|
||||
|
||||
$scope.entity = newCredential.entity
|
||||
$scope.username = newCredential.username
|
||||
$scope.password = newCredential.password
|
||||
$scope.addNewCredentialInfo()
|
||||
|
||||
$httpBackend.flush()
|
||||
|
||||
expect($scope.credentialInfo[1]).toEqual(newCredential)
|
||||
|
||||
$httpBackend.verifyNoOutstandingExpectation()
|
||||
$httpBackend.verifyNoOutstandingRequest()
|
||||
})
|
||||
|
||||
function createController() {
|
||||
return $controller('CredentialCtrl', { $scope: $scope, baseUrlSrv: baseUrlSrvMock, })
|
||||
}
|
||||
})
|
||||
|
|
@ -288,7 +288,7 @@ limitations under the License.
|
|||
type="text" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
|
||||
<input ng-if="newInterpreterSetting.properties[key].type === 'password'"
|
||||
type="password" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
|
||||
<input ng-if="newInterpreterSetting.properties[key].type === 'boolean'"
|
||||
<input ng-if="newInterpreterSetting.properties[key].type === 'checkbox'"
|
||||
type="checkbox" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
|
|
|
|||
54
zeppelin-web/src/app/jobmanager/job-status.js
Normal file
54
zeppelin-web/src/app/jobmanager/job-status.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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',
|
||||
}
|
||||
|
||||
export function getJobIconByStatus(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'
|
||||
}
|
||||
}
|
||||
|
||||
export function getJobColorByStatus(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'
|
||||
}
|
||||
}
|
||||
160
zeppelin-web/src/app/jobmanager/job/job.component.js
Normal file
160
zeppelin-web/src/app/jobmanager/job/job.component.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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 moment from 'moment'
|
||||
|
||||
import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status'
|
||||
import { getJobColorByStatus, getJobIconByStatus } from '../job-status'
|
||||
|
||||
import jobTemplate from './job.html'
|
||||
import './job.css'
|
||||
|
||||
class JobController {
|
||||
constructor($http, JobManagerService) {
|
||||
'ngInject'
|
||||
this.$http = $http
|
||||
this.JobManagerService = JobManagerService
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.note.isRunningJob
|
||||
}
|
||||
|
||||
getParagraphs() {
|
||||
return this.note.paragraphs
|
||||
}
|
||||
|
||||
getNoteId() {
|
||||
return this.note.noteId
|
||||
}
|
||||
|
||||
getNoteName() {
|
||||
return this.note.noteName
|
||||
}
|
||||
|
||||
runJob() {
|
||||
BootstrapDialog.confirm({
|
||||
closable: true,
|
||||
title: 'Job Dialog',
|
||||
message: 'Run all paragraphs?',
|
||||
callback: clickOk => {
|
||||
if (!clickOk) { return }
|
||||
|
||||
const noteId = this.getNoteId()
|
||||
// if the request is handled successfully, the job page will get updated using websocket
|
||||
this.JobManagerService.sendRunJobRequest(noteId)
|
||||
.catch(response => {
|
||||
let message = (response.data && response.data.message)
|
||||
? response.data.message : 'SERVER ERROR'
|
||||
this.showErrorDialog('Execution Failure', message)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
stopJob() {
|
||||
BootstrapDialog.confirm({
|
||||
closable: true,
|
||||
title: 'Job Dialog',
|
||||
message: 'Stop all paragraphs?',
|
||||
callback: clickOk => {
|
||||
if (!clickOk) { return }
|
||||
|
||||
const noteId = this.getNoteId()
|
||||
// if the request is handled successfully, the job page will get updated using websocket
|
||||
this.JobManagerService.sendStopJobRequest(noteId)
|
||||
.catch(response => {
|
||||
let message = (response.data && response.data.message)
|
||||
? response.data.message : 'SERVER ERROR'
|
||||
this.showErrorDialog('Stop Failure', message)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
showErrorDialog(title, errorMessage) {
|
||||
if (!errorMessage) { errorMessage = 'SERVER ERROR' }
|
||||
BootstrapDialog.alert({
|
||||
closable: true,
|
||||
title: title,
|
||||
message: errorMessage
|
||||
})
|
||||
}
|
||||
|
||||
lastExecuteTime() {
|
||||
const timestamp = this.note.unixTimeLastRun
|
||||
return moment.unix(timestamp / 1000).fromNow()
|
||||
}
|
||||
|
||||
getInterpreterName() {
|
||||
return typeof this.note.interpreter === 'undefined'
|
||||
? 'interpreter is not set' : this.note.interpreter
|
||||
}
|
||||
|
||||
getInterpreterNameStyle() {
|
||||
return typeof this.note.interpreter === 'undefined'
|
||||
? { color: 'gray' } : { color: 'black' }
|
||||
}
|
||||
|
||||
getJobTypeIcon() {
|
||||
const noteType = this.note.noteType
|
||||
if (noteType === 'normal') {
|
||||
return 'icon-doc'
|
||||
} else if (noteType === 'cron') {
|
||||
return 'icon-clock'
|
||||
} else {
|
||||
return 'icon-question'
|
||||
}
|
||||
}
|
||||
|
||||
getJobColorByStatus(status) {
|
||||
return getJobColorByStatus(status)
|
||||
}
|
||||
|
||||
getJobIconByStatus(status) {
|
||||
return getJobIconByStatus(status)
|
||||
}
|
||||
|
||||
getProgress() {
|
||||
const paragraphs = this.getParagraphs()
|
||||
let paragraphStatuses = paragraphs.map(p => p.status)
|
||||
let runningOrFinishedParagraphs = paragraphStatuses.filter(status => {
|
||||
return status === ParagraphStatus.RUNNING || status === ParagraphStatus.FINISHED
|
||||
})
|
||||
|
||||
let totalCount = paragraphStatuses.length
|
||||
let runningCount = runningOrFinishedParagraphs.length
|
||||
let result = Math.ceil(runningCount / totalCount * 100)
|
||||
result = isNaN(result) ? 0 : result
|
||||
|
||||
return `${result}%`
|
||||
}
|
||||
|
||||
showPercentProgressBar() {
|
||||
return this.getProgress() > 0 && this.getProgress() < 100
|
||||
}
|
||||
}
|
||||
|
||||
export const JobComponent = {
|
||||
bindings: {
|
||||
note: '<',
|
||||
},
|
||||
template: jobTemplate,
|
||||
controller: JobController,
|
||||
}
|
||||
|
||||
export const JobModule = angular
|
||||
.module('zeppelinWebApp')
|
||||
.component('job', JobComponent)
|
||||
.name
|
||||
63
zeppelin-web/src/app/jobmanager/job/job.component.test.js
Normal file
63
zeppelin-web/src/app/jobmanager/job/job.component.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { ParagraphStatus } from '../../notebook/paragraph/paragraph.status'
|
||||
|
||||
describe('JobComponent', () => {
|
||||
let $componentController
|
||||
|
||||
beforeEach(angular.mock.module('zeppelinWebApp'))
|
||||
beforeEach(angular.mock.inject((_$componentController_) => {
|
||||
$componentController = _$componentController_
|
||||
}))
|
||||
|
||||
it('should get progress when there is a finished paragraph', () => {
|
||||
const paragraphs = [
|
||||
{ status: ParagraphStatus.FINISHED },
|
||||
]
|
||||
const mockNote = createMockNote(paragraphs)
|
||||
const bindings = { note: mockNote, }
|
||||
|
||||
const ctrl = $componentController('job', null, bindings)
|
||||
expect(ctrl).toBeDefined()
|
||||
|
||||
const progress1 = ctrl.getProgress()
|
||||
expect(progress1).toBe('100%')
|
||||
})
|
||||
|
||||
it('should get progress when there is pending and finished paragraphs', () => {
|
||||
const paragraphs = [
|
||||
{ status: ParagraphStatus.PENDING },
|
||||
{ status: ParagraphStatus.FINISHED},
|
||||
]
|
||||
const mockNote = createMockNote(paragraphs)
|
||||
const bindings = { note: mockNote, }
|
||||
|
||||
const ctrl = $componentController('job', null, bindings)
|
||||
|
||||
const progress1 = ctrl.getProgress()
|
||||
expect(progress1).toBe('50%')
|
||||
})
|
||||
|
||||
it('should get proper job type icons', () => {
|
||||
const paragraphs = [ { status: ParagraphStatus.PENDING }, ]
|
||||
const mockNote = createMockNote(paragraphs)
|
||||
const bindings = { note: mockNote, }
|
||||
|
||||
const ctrl = $componentController('job', null, bindings)
|
||||
|
||||
let icon = ctrl.getJobTypeIcon()
|
||||
expect(icon).toBe('icon-doc')
|
||||
|
||||
mockNote.noteType = 'cron'
|
||||
icon = ctrl.getJobTypeIcon()
|
||||
expect(icon).toBe('icon-clock')
|
||||
})
|
||||
|
||||
function createMockNote(paragraphs) {
|
||||
return {
|
||||
isRunningJob: false,
|
||||
paragraphs: paragraphs,
|
||||
noteId: 'NT01',
|
||||
noteName: 'TestNote01',
|
||||
noteType: 'normal',
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -80,6 +80,11 @@
|
|||
z-index: 10003;
|
||||
}
|
||||
|
||||
.job .control .job-control-btn {
|
||||
cursor: pointer;
|
||||
color: #3071A9;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spinnerRotateAnimation
|
||||
{
|
||||
from{-webkit-transform:rotate(0deg);}
|
||||
69
zeppelin-web/src/app/jobmanager/job/job.html
Normal file
69
zeppelin-web/src/app/jobmanager/job/job.html
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<!--
|
||||
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="job" >
|
||||
<div>
|
||||
<!-- job control: start -->
|
||||
<div id="{{$ctrl.getNoteId()}}_control" class="control">
|
||||
<span ng-bind="$ctrl.lastExecuteTime()"></span>
|
||||
<span>
|
||||
<span ng-bind="$ctrl.isRunning() ? 'RUNNING' : 'READY'"></span>
|
||||
</span>
|
||||
|
||||
<span ng-if="$ctrl.isRunning()" ng-bind="$ctrl.getProgress()"></span>
|
||||
|
||||
<span
|
||||
class="job-control-btn" tooltip-placement="left"
|
||||
uib-tooltip-html="!$ctrl.isRunning() ? 'Start All Paragraphs' : 'Stop All Paragraphs'"
|
||||
ng-click="!$ctrl.isRunning() ? $ctrl.runJob() : $ctrl.stopJob()"
|
||||
ng-class="!$ctrl.isRunning() ? 'icon-control-play' : 'icon-control-pause'">
|
||||
</span>
|
||||
</div>
|
||||
<!-- job control: end -->
|
||||
|
||||
<span class="job-types">
|
||||
<i ng-class="$ctrl.getJobTypeIcon()"></i>
|
||||
</span>
|
||||
<a style="text-decoration: none !important;" ng-href="#/notebook/{{$ctrl.getNoteId()}}">
|
||||
<span ng-bind="$ctrl.getNoteName() + ' - '"></span>
|
||||
<span ng-style="$ctrl.getInterpreterNameStyle()"
|
||||
ng-bind="$ctrl.getInterpreterName()">
|
||||
</span>
|
||||
</a>
|
||||
<!-- job progress bar: start -->
|
||||
<div id="{{$ctrl.getNoteId()}}_runControl" class="runControl">
|
||||
<div id="{{$ctrl.getNoteId()}}_progress" class="progress" ng-if="$ctrl.isRunning() === true">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
ng-style="$ctrl.showPercentProgressBar() ? { 'width': $ctrl.getProgress() } : { 'width': '100%' }"
|
||||
ng-class="$ctrl.showPercentProgressBar() ? '' : 'progress-bar-striped active'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- job progress bar: end -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span ng-repeat="paragraph in $ctrl.getParagraphs()">
|
||||
<a style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{$ctrl.getNoteId()}}?paragraph={{paragraph.id}}">
|
||||
<i ng-style="{'color': $ctrl.getJobColorByStatus(paragraph.status)}"
|
||||
ng-class="$ctrl.getJobIconByStatus(paragraph.status)"
|
||||
tooltip-placement="top-left"
|
||||
uib-tooltip="{{paragraph.name}} is {{paragraph.status}}">
|
||||
</i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
186
zeppelin-web/src/app/jobmanager/jobmanager.component.js
Normal file
186
zeppelin-web/src/app/jobmanager/jobmanager.component.js
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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 './job/job.component'
|
||||
import { JobManagerFilter } from './jobmanager.filter'
|
||||
import { JobManagerService} from './jobmanager.service'
|
||||
|
||||
import { getJobIconByStatus, getJobColorByStatus } from './job-status'
|
||||
|
||||
angular.module('zeppelinWebApp')
|
||||
.controller('JobManagerCtrl', JobManagerController)
|
||||
.filter('JobManager', JobManagerFilter)
|
||||
.service('JobManagerService', JobManagerService)
|
||||
|
||||
const JobDateSorter = {
|
||||
RECENTLY_UPDATED: 'Recently Update',
|
||||
OLDEST_UPDATED: 'Oldest Updated',
|
||||
}
|
||||
|
||||
function JobManagerController($scope, ngToast, JobManagerFilter, JobManagerService) {
|
||||
'ngInject'
|
||||
|
||||
$scope.isFilterLoaded = false
|
||||
$scope.jobs = []
|
||||
$scope.sorter = {
|
||||
availableDateSorter: Object.keys(JobDateSorter).map(key => { return JobDateSorter[key] }),
|
||||
currentDateSorter: JobDateSorter.RECENTLY_UPDATED,
|
||||
}
|
||||
$scope.filteredJobs = $scope.jobs
|
||||
$scope.filterConfig = {
|
||||
isRunningAlwaysTop: true,
|
||||
noteNameFilterValue: '',
|
||||
interpreterFilterValue: '*',
|
||||
isSortByAsc: true,
|
||||
}
|
||||
|
||||
$scope.pagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 10,
|
||||
maxPageCount: 5,
|
||||
}
|
||||
|
||||
ngToast.dismiss()
|
||||
init()
|
||||
|
||||
/** functions */
|
||||
|
||||
$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))
|
||||
}
|
||||
|
||||
let asyncNotebookJobFilter = function (jobs, filterConfig) {
|
||||
return new Promise((resolve, reject) => {
|
||||
$scope.filteredJobs = JobManagerFilter(jobs, filterConfig)
|
||||
resolve($scope.filteredJobs)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.$watch('sorter.currentDateSorter', function() {
|
||||
$scope.filterConfig.isSortByAsc =
|
||||
$scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED
|
||||
asyncNotebookJobFilter($scope.jobs, $scope.filterConfig)
|
||||
})
|
||||
|
||||
$scope.getJobIconByStatus = getJobIconByStatus
|
||||
$scope.getJobColorByStatus = getJobColorByStatus
|
||||
|
||||
$scope.filterJobs = function (jobs, filterConfig) {
|
||||
asyncNotebookJobFilter(jobs, filterConfig)
|
||||
.then(() => {
|
||||
$scope.isFilterLoaded = true
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to search jobs from server', error)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.filterValueToName = function (filterValue, maxStringLength) {
|
||||
if (typeof $scope.defaultInterpreters === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
let index = $scope.defaultInterpreters.findIndex(intp => intp.value === filterValue)
|
||||
if (typeof $scope.defaultInterpreters[index].name !== 'undefined') {
|
||||
if (typeof maxStringLength !== 'undefined' &&
|
||||
maxStringLength > $scope.defaultInterpreters[index].name) {
|
||||
return $scope.defaultInterpreters[index].name.substr(0, maxStringLength - 3) + '...'
|
||||
}
|
||||
return $scope.defaultInterpreters[index].name
|
||||
} else {
|
||||
return 'NONE'
|
||||
}
|
||||
}
|
||||
|
||||
$scope.setFilterValue = function (filterValue) {
|
||||
$scope.filterConfig.interpreterFilterValue = filterValue
|
||||
$scope.filterJobs($scope.jobs, $scope.filterConfig)
|
||||
}
|
||||
|
||||
$scope.setJobs = function(jobs) {
|
||||
$scope.jobs = jobs
|
||||
let interpreters = $scope.jobs
|
||||
.filter(j => typeof j.interpreter !== 'undefined')
|
||||
.map(j => j.interpreter)
|
||||
interpreters = [...new Set(interpreters)] // remove duplicated interpreters
|
||||
|
||||
$scope.defaultInterpreters = [ { name: 'ALL', value: '*' } ]
|
||||
for (let i = 0; i < interpreters.length; i++) {
|
||||
$scope.defaultInterpreters.push({ name: interpreters[i], value: interpreters[i] })
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
JobManagerService.getJobs()
|
||||
JobManagerService.subscribeSetJobs($scope, setJobsCallback)
|
||||
JobManagerService.subscribeUpdateJobs($scope, updateJobsCallback)
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
JobManagerService.disconnect()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
** $scope.$on functions below
|
||||
*/
|
||||
|
||||
function setJobsCallback(event, response) {
|
||||
const jobs = response.jobs
|
||||
$scope.setJobs(jobs)
|
||||
$scope.filterJobs($scope.jobs, $scope.filterConfig)
|
||||
}
|
||||
|
||||
function updateJobsCallback(event, response) {
|
||||
let jobs = $scope.jobs
|
||||
let jobByNoteId = jobs.reduce((acc, j) => {
|
||||
const noteId = j.noteId
|
||||
acc[noteId] = j
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
let updatedJobs = response.jobs
|
||||
updatedJobs.map(updatedJob => {
|
||||
if (typeof jobByNoteId[updatedJob.noteId] === 'undefined') {
|
||||
let newItem = angular.copy(updatedJob)
|
||||
jobs.push(newItem)
|
||||
jobByNoteId[updatedJob.noteId] = newItem
|
||||
} else {
|
||||
let job = jobByNoteId[updatedJob.noteId]
|
||||
|
||||
if (updatedJob.isRemoved === true) {
|
||||
delete jobByNoteId[updatedJob.noteId]
|
||||
let removeIndex = jobs.findIndex(j => j.noteId === updatedJob.noteId)
|
||||
if (removeIndex) {
|
||||
jobs.splice(removeIndex, 1)
|
||||
}
|
||||
} else {
|
||||
// update the job
|
||||
job.isRunningJob = updatedJob.isRunningJob
|
||||
job.noteName = updatedJob.noteName
|
||||
job.noteType = updatedJob.noteType
|
||||
job.interpreter = updatedJob.interpreter
|
||||
job.unixTimeLastRun = updatedJob.unixTimeLastRun
|
||||
job.paragraphs = updatedJob.paragraphs
|
||||
}
|
||||
}
|
||||
})
|
||||
$scope.filterJobs(jobs, $scope.filterConfig)
|
||||
}
|
||||
}
|
||||
26
zeppelin-web/src/app/jobmanager/jobmanager.component.test.js
Normal file
26
zeppelin-web/src/app/jobmanager/jobmanager.component.test.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
describe('JobManagerComponent', () => {
|
||||
let $scope
|
||||
let $controller
|
||||
|
||||
beforeEach(angular.mock.module('zeppelinWebApp'))
|
||||
beforeEach(angular.mock.inject((_$rootScope_, _$controller_) => {
|
||||
$scope = _$rootScope_.$new()
|
||||
$controller = _$controller_
|
||||
}))
|
||||
|
||||
it('should set jobs using `setJobs`', () => {
|
||||
let ctrl = $controller('JobManagerCtrl', { $scope: $scope, })
|
||||
expect(ctrl).toBeDefined()
|
||||
|
||||
const mockJobs = [
|
||||
{ noteId: 'TN01', interpreter: 'spark', },
|
||||
{ noteId: 'TN02', interpreter: 'spark', },
|
||||
]
|
||||
|
||||
$scope.setJobs(mockJobs)
|
||||
expect($scope.defaultInterpreters).toEqual([
|
||||
{ name: 'ALL', value: '*', },
|
||||
{ name: 'spark', value: 'spark', },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* 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 { JobStatus, } from './jobs/job-status'
|
||||
|
||||
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) {
|
||||
$scope.JobInfomationsByFilter = $scope.jobTypeFilter(jobInfomations, filterConfig)
|
||||
resolve($scope.JobInfomationsByFilter)
|
||||
})
|
||||
}
|
||||
|
||||
$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(
|
||||
() => { $scope.isLoadingFilter = false },
|
||||
(error) => {
|
||||
console.error('Failed to search jobs from server', error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$scope.filterValueToName = function (filterValue, maxStringLength) {
|
||||
if ($scope.activeInterpreters === undefined) {
|
||||
return
|
||||
}
|
||||
let index = _.findIndex($scope.activeInterpreters, {value: filterValue})
|
||||
if ($scope.activeInterpreters[index].name !== undefined) {
|
||||
if (maxStringLength !== undefined && maxStringLength > $scope.activeInterpreters[index].name) {
|
||||
return $scope.activeInterpreters[index].name.substr(0, maxStringLength - 3) + '...'
|
||||
}
|
||||
return $scope.activeInterpreters[index].name
|
||||
} else {
|
||||
return 'NONE'
|
||||
}
|
||||
}
|
||||
|
||||
$scope.setFilterValue = function (filterValue) {
|
||||
$scope.filterConfig.filterValueInterpreter = filterValue
|
||||
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
|
||||
}
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.isLoadingFilter = true
|
||||
$scope.jobInfomations = []
|
||||
$scope.JobInfomationsByFilter = $scope.jobInfomations
|
||||
$scope.filterConfig = {
|
||||
isRunningAlwaysTop: true,
|
||||
filterValueNotebookName: '',
|
||||
filterValueInterpreter: '*',
|
||||
isSortByAsc: $scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED,
|
||||
}
|
||||
$scope.sortTooltipMsg = 'Switch to sort by desc'
|
||||
$scope.jobTypeFilter = jobManagerFilter
|
||||
|
||||
websocketMsgSrv.getNoteJobsList()
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
websocketMsgSrv.unsubscribeJobManager()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
** $scope.$on functions below
|
||||
*/
|
||||
|
||||
$scope.$on('setNoteJobs', function (event, responseData) {
|
||||
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime
|
||||
$scope.jobInfomations = responseData.jobs
|
||||
$scope.jobInfomationsIndexs = $scope.jobInfomations ? _.indexBy($scope.jobInfomations, 'noteId') : {}
|
||||
$scope.jobTypeFilter($scope.jobInfomations, $scope.filterConfig)
|
||||
$scope.activeInterpreters = [
|
||||
{
|
||||
name: 'ALL',
|
||||
value: '*'
|
||||
}
|
||||
]
|
||||
let interpreterLists = _.uniq(_.pluck($scope.jobInfomations, 'interpreter'), false)
|
||||
for (let index = 0, length = interpreterLists.length; index < length; index++) {
|
||||
$scope.activeInterpreters.push({
|
||||
name: interpreterLists[index],
|
||||
value: interpreterLists[index]
|
||||
})
|
||||
}
|
||||
$scope.doFiltering($scope.jobInfomations, $scope.filterConfig)
|
||||
})
|
||||
|
||||
$scope.$on('setUpdateNoteJobs', function (event, responseData) {
|
||||
let jobInfomations = $scope.jobInfomations
|
||||
let indexStore = $scope.jobInfomationsIndexs
|
||||
$scope.lastJobServerUnixTime = responseData.lastResponseUnixTime
|
||||
let notes = responseData.jobs
|
||||
notes.map(function (changedItem) {
|
||||
if (indexStore[changedItem.noteId] === undefined) {
|
||||
let newItem = angular.copy(changedItem)
|
||||
jobInfomations.push(newItem)
|
||||
indexStore[changedItem.noteId] = newItem
|
||||
} else {
|
||||
let changeOriginTarget = indexStore[changedItem.noteId]
|
||||
|
||||
if (changedItem.isRemoved !== undefined && changedItem.isRemoved === true) {
|
||||
// remove Item.
|
||||
let removeIndex = _.findIndex(indexStore, changedItem.noteId)
|
||||
if (removeIndex > -1) {
|
||||
indexStore.splice(removeIndex, 1)
|
||||
}
|
||||
|
||||
removeIndex = _.findIndex(jobInfomations, {'noteId': changedItem.noteId})
|
||||
if (removeIndex) {
|
||||
jobInfomations.splice(removeIndex, 1)
|
||||
}
|
||||
} else {
|
||||
// change value for item.
|
||||
changeOriginTarget.isRunningJob = changedItem.isRunningJob
|
||||
changeOriginTarget.noteName = changedItem.noteName
|
||||
changeOriginTarget.noteType = changedItem.noteType
|
||||
changeOriginTarget.interpreter = changedItem.interpreter
|
||||
changeOriginTarget.unixTimeLastRun = changedItem.unixTimeLastRun
|
||||
changeOriginTarget.paragraphs = changedItem.paragraphs
|
||||
}
|
||||
}
|
||||
})
|
||||
$scope.doFiltering(jobInfomations, $scope.filterConfig)
|
||||
})
|
||||
}
|
||||
|
|
@ -12,36 +12,37 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
angular.module('zeppelinWebApp').filter('jobManager', jobManagerFilter)
|
||||
|
||||
function jobManagerFilter () {
|
||||
function filterContext (jobItems, filterConfig) {
|
||||
let filterValueInterpreter = filterConfig.filterValueInterpreter
|
||||
let filterValueNotebookName = filterConfig.filterValueNotebookName
|
||||
export function JobManagerFilter() {
|
||||
function filterContext (jobs, filterConfig) {
|
||||
let interpreter = filterConfig.interpreterFilterValue
|
||||
let noteName = filterConfig.noteNameFilterValue
|
||||
let isSortByAsc = filterConfig.isSortByAsc
|
||||
let filterItems = jobItems
|
||||
let filteredJobs = jobs
|
||||
|
||||
if (filterValueInterpreter === undefined) {
|
||||
filterItems = filterItems.filter((jobItem) => {
|
||||
return jobItem.interpreter === undefined
|
||||
if (typeof interpreter === 'undefined') {
|
||||
filteredJobs = filteredJobs.filter((jobItem) => {
|
||||
return typeof jobItem.interpreter === 'undefined'
|
||||
})
|
||||
} else if (filterValueInterpreter !== '*') {
|
||||
filterItems = _.where(filterItems, {interpreter: filterValueInterpreter})
|
||||
} else if (interpreter !== '*') {
|
||||
filteredJobs = filteredJobs.filter(j => j.interpreter === interpreter)
|
||||
}
|
||||
|
||||
if (filterValueNotebookName !== '') {
|
||||
filterItems = filterItems.filter((jobItem) => {
|
||||
let lowerFilterValue = filterValueNotebookName.toLocaleLowerCase()
|
||||
// filter by note name
|
||||
if (noteName !== '') {
|
||||
filteredJobs = filteredJobs.filter((jobItem) => {
|
||||
let lowerFilterValue = noteName.toLocaleLowerCase()
|
||||
let lowerNotebookName = jobItem.noteName.toLocaleLowerCase()
|
||||
return lowerNotebookName.match(new RegExp('.*' + lowerFilterValue + '.*'))
|
||||
})
|
||||
}
|
||||
|
||||
filterItems = filterItems.sort((jobItem) => {
|
||||
// sort by name
|
||||
filteredJobs = filteredJobs.sort((jobItem) => {
|
||||
return jobItem.noteName.toLowerCase()
|
||||
})
|
||||
|
||||
filterItems = filterItems.sort((x, y) => {
|
||||
// sort by timestamp
|
||||
filteredJobs = filteredJobs.sort((x, y) => {
|
||||
if (isSortByAsc) {
|
||||
return x.unixTimeLastRun - y.unixTimeLastRun
|
||||
} else {
|
||||
|
|
@ -49,7 +50,7 @@ function jobManagerFilter () {
|
|||
}
|
||||
})
|
||||
|
||||
return filterItems
|
||||
return filteredJobs
|
||||
}
|
||||
return filterContext
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ 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 id="job-manager-header" class="job-manager-header" data-ng-init="init()">
|
||||
<div id="job-manager-header" class="job-manager-header">
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
|
@ -40,9 +39,9 @@ limitations under the License.
|
|||
<input class="form-control btn-xs"
|
||||
placeholder=" Search jobs..."
|
||||
type="text"
|
||||
ng-model="filterConfig.filterValueNotebookName"
|
||||
ng-model="filterConfig.noteNameFilterValue"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }"
|
||||
ng-change="doFiltering(jobInfomations, filterConfig)" />
|
||||
ng-change="filterJobs(jobs, filterConfig)" />
|
||||
</div>
|
||||
|
||||
<!-- search tool: default interpreter dropdown -->
|
||||
|
|
@ -50,17 +49,17 @@ limitations under the License.
|
|||
data-toggle="dropdown">
|
||||
<span>
|
||||
<span class="dropdown-text-desc">Interpreter: </span>
|
||||
<span class="dropdown-text-value">{{filterValueToName(filterConfig.filterValueInterpreter)}}</span>
|
||||
<span class="dropdown-text-value">{{filterValueToName(filterConfig.interpreterFilterValue)}}</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 ng-click="setFilterValue(interpreterOption.value)"
|
||||
ng-style="(filterValueToName(interpreterOption.value) === 'ALL' || filterValueToName(interpreterOption.value) === 'NONE') ? { 'font-weight': 500 } : {}"
|
||||
<li ng-repeat="interpreter in defaultInterpreters">
|
||||
<a ng-click="setFilterValue(interpreter.value)"
|
||||
ng-style="(filterValueToName(interpreter.value) === 'ALL' || filterValueToName(interpreter.value) === 'NONE') ? { 'font-weight': 500 } : {}"
|
||||
class="dropdown-list-value">
|
||||
{{filterValueToName(interpreterOption.value)}}
|
||||
{{filterValueToName(interpreter.value)}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -78,7 +77,7 @@ limitations under the License.
|
|||
</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-right search-tool-dropdown-content" role="menu">
|
||||
<li ng-repeat="dateSorter in sorter.AvailableDateSorter">
|
||||
<li ng-repeat="dateSorter in sorter.availableDateSorter">
|
||||
<a ng-click="setJobDateSorter(dateSorter)" class="dropdown-list-value">
|
||||
{{dateSorter}}
|
||||
</a>
|
||||
|
|
@ -87,7 +86,7 @@ limitations under the License.
|
|||
</span>
|
||||
<span class="job-counter">
|
||||
<span class="job-counter-label">Total: </span>
|
||||
<span class="job-counter-value">{{JobInfomationsByFilter.length}}</span>
|
||||
<span class="job-counter-value">{{filteredJobs.length}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -97,8 +96,9 @@ limitations under the License.
|
|||
<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}}
|
||||
ng-style="{'color': getJobColorByStatus(jobStatus)}"
|
||||
ng-class="getJobIconByStatus(jobStatus)" ></i>
|
||||
{{jobStatus}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -110,20 +110,20 @@ limitations under the License.
|
|||
|
||||
<div>
|
||||
<div class="note-jump"></div>
|
||||
<div ng-if="isLoadingFilter === true" class="paragraph-col">
|
||||
<div ng-if="!isFilterLoaded" class="paragraph-col">
|
||||
<div class="job-space box job-margin text-center">
|
||||
<i style="color: blue" class="fa fa-spinner spinAnimation"></i>Loading...
|
||||
<i style="color: blue" class="fa fa-spinner spinAnimation"></i>
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="JobInfomationsByFilter.length > 0"
|
||||
ng-repeat="notebookJob in getJobsInCurrentPage(JobInfomationsByFilter)"
|
||||
<div ng-if="filteredJobs.length > 0"
|
||||
ng-repeat="note in getJobsInCurrentPage(filteredJobs)"
|
||||
class="paragraph-col">
|
||||
<div ng-include src="'app/jobmanager/jobs/job.html'"
|
||||
class="job-space box job-margin"
|
||||
ng-controller="JobCtrl">
|
||||
<div class="job-space box job-margin">
|
||||
<job note="note"></job>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="isLoadingFilter === false && JobInfomationsByFilter.length <= 0"
|
||||
<div ng-if="isFilterLoaded === false && filteredJobs.length <= 0"
|
||||
class="paragraph-col">
|
||||
<div class="job-space box job-margin text-center">No Job found</div>
|
||||
</div>
|
||||
|
|
@ -131,7 +131,7 @@ limitations under the License.
|
|||
<!-- pagination -->
|
||||
<div class="job-pagination-container">
|
||||
<ul uib-pagination class="pagination-sm"
|
||||
total-items="JobInfomationsByFilter.length"
|
||||
total-items="filteredJobs.length"
|
||||
ng-model="pagination.currentPage"
|
||||
items-per-page="pagination.itemsPerPage"
|
||||
boundary-links="true" rotate="false"
|
||||
|
|
|
|||
64
zeppelin-web/src/app/jobmanager/jobmanager.service.js
Normal file
64
zeppelin-web/src/app/jobmanager/jobmanager.service.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class JobManagerService {
|
||||
constructor($http, $rootScope, baseUrlSrv, websocketMsgSrv) {
|
||||
'ngInject'
|
||||
|
||||
this.$http = $http
|
||||
this.$rootScope = $rootScope
|
||||
this.BaseUrlService = baseUrlSrv
|
||||
this.WebsocketMessageService = websocketMsgSrv
|
||||
}
|
||||
|
||||
sendStopJobRequest(noteId) {
|
||||
const apiURL = this.BaseUrlService.getRestApiBase() + `/notebook/job/${noteId}`
|
||||
return this.$http({ method: 'DELETE', url: apiURL, })
|
||||
}
|
||||
|
||||
sendRunJobRequest(noteId) {
|
||||
const apiURL = this.BaseUrlService.getRestApiBase() + `/notebook/job/${noteId}`
|
||||
return this.$http({ method: 'POST', url: apiURL, })
|
||||
}
|
||||
|
||||
getJobs() {
|
||||
this.WebsocketMessageService.getJobs()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.WebsocketMessageService.disconnectJobEvent()
|
||||
}
|
||||
|
||||
subscribeSetJobs(controllerScope, receiveCallback) {
|
||||
const event = 'jobmanager:set-jobs'
|
||||
console.log(`(Event) Subscribed: ${event}`)
|
||||
const unsubscribeHandler = this.$rootScope.$on(event, receiveCallback)
|
||||
|
||||
controllerScope.$on('$destroy', () => {
|
||||
console.log(`(Event) Unsubscribed: ${event}`)
|
||||
unsubscribeHandler()
|
||||
})
|
||||
}
|
||||
|
||||
subscribeUpdateJobs(controllerScope, receiveCallback) {
|
||||
const event = 'jobmanager:update-jobs'
|
||||
console.log(`(Event) Subscribed: ${event}`)
|
||||
const unsubscribeHandler = this.$rootScope.$on(event, receiveCallback)
|
||||
|
||||
controllerScope.$on('$destroy', () => {
|
||||
console.log(`(Event) Unsubscribed: ${event}`)
|
||||
unsubscribeHandler()
|
||||
})
|
||||
}
|
||||
}
|
||||
65
zeppelin-web/src/app/jobmanager/jobmanager.service.test.js
Normal file
65
zeppelin-web/src/app/jobmanager/jobmanager.service.test.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { ParagraphStatus } from '../notebook/paragraph/paragraph.status'
|
||||
import { JobManagerService } from './jobmanager.service'
|
||||
|
||||
describe('JobManagerService', () => {
|
||||
const baseUrlSrvMock = { getRestApiBase: () => '' }
|
||||
let service
|
||||
let $httpBackend
|
||||
|
||||
beforeEach(angular.mock.module('zeppelinWebApp'))
|
||||
beforeEach(angular.mock.inject((_$rootScope_, _$httpBackend_, _$http_, _websocketMsgSrv_) => {
|
||||
$httpBackend = _$httpBackend_
|
||||
service = new JobManagerService(_$http_, _$rootScope_, baseUrlSrvMock, _websocketMsgSrv_)
|
||||
}))
|
||||
|
||||
it('should sent valid request to run a job', () => {
|
||||
const paragraphs = [ { status: ParagraphStatus.PENDING }, ]
|
||||
const mockNote = createMockNote(paragraphs)
|
||||
|
||||
const noteId = mockNote.noteId
|
||||
service.sendRunJobRequest(noteId)
|
||||
|
||||
const url = `/notebook/job/${noteId}`
|
||||
|
||||
$httpBackend
|
||||
.when('POST', url)
|
||||
.respond(200, { /** return nothing */ })
|
||||
$httpBackend.expectPOST(url)
|
||||
$httpBackend.flush()
|
||||
|
||||
checkUnknownHttpRequests()
|
||||
})
|
||||
|
||||
it('should sent valid request to stop a job', () => {
|
||||
const paragraphs = [ { status: ParagraphStatus.PENDING }, ]
|
||||
const mockNote = createMockNote(paragraphs)
|
||||
|
||||
const noteId = mockNote.noteId
|
||||
service.sendStopJobRequest(noteId)
|
||||
|
||||
const url = `/notebook/job/${noteId}`
|
||||
|
||||
$httpBackend
|
||||
.when('DELETE', url)
|
||||
.respond(200, { /** return nothing */ })
|
||||
$httpBackend.expectDELETE(url)
|
||||
$httpBackend.flush()
|
||||
|
||||
checkUnknownHttpRequests()
|
||||
})
|
||||
|
||||
function checkUnknownHttpRequests() {
|
||||
$httpBackend.verifyNoOutstandingExpectation()
|
||||
$httpBackend.verifyNoOutstandingRequest()
|
||||
}
|
||||
|
||||
function createMockNote(paragraphs) {
|
||||
return {
|
||||
isRunningJob: false,
|
||||
paragraphs: paragraphs,
|
||||
noteId: 'NT01',
|
||||
noteName: 'TestNote01',
|
||||
noteType: 'normal',
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<!--
|
||||
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 id="{{notebookJob.noteId}}_control" class="control">
|
||||
<span>
|
||||
{{lastExecuteTime(notebookJob.unixTimeLastRun)}}
|
||||
</span>
|
||||
<span>
|
||||
<span ng-if="notebookJob.isRunningJob">
|
||||
RUNNING
|
||||
</span>
|
||||
<span ng-if="!notebookJob.isRunningJob">
|
||||
READY
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span ng-if="notebookJob.isRunningJob">
|
||||
{{getProgress()}}%
|
||||
</span>
|
||||
<!-- Run / Cancel button -->
|
||||
<span
|
||||
ng-if="!notebookJob.isRunningJob"
|
||||
class="icon-control-play" style="cursor:pointer;color:#3071A9" tooltip-placement="left" uib-tooltip="START ALL Job"
|
||||
ng-click="runNotebookJob(notebookJob.noteId)">
|
||||
</span>
|
||||
<span
|
||||
ng-if="notebookJob.isRunningJob"
|
||||
class="icon-control-pause" style="cursor:pointer;color:#3071A9" tooltip-placement="left" uib-tooltip="STOP ALL Job"
|
||||
ng-click="stopNotebookJob(notebookJob.noteId)">
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<!--
|
||||
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 id="{{notebookJob.noteId}}_runControl" class="runControl">
|
||||
<div id="{{notebookJob.noteId}}_progress" class="progress" ng-if="notebookJob.isRunningJob === true">
|
||||
<div ng-if="getProgress()>0 && getProgress()<100 && notebookJob.isRunningJob === true"
|
||||
class="progress-bar" role="progressbar" ng-style="{width:getProgress()+'%'}"></div>
|
||||
<div ng-if="(getProgress()<=0 || getProgress()>=100) && (notebookJob.isRunningJob === true)"
|
||||
class="progress-bar progress-bar-striped active" role="progressbar" style="width:100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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',
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* 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 moment from 'moment'
|
||||
|
||||
import { ParagraphStatus, } from '../../notebook/paragraph/paragraph.status'
|
||||
|
||||
angular.module('zeppelinWebApp').controller('JobCtrl', JobCtrl)
|
||||
|
||||
function JobCtrl ($scope, $http, baseUrlSrv) {
|
||||
'ngInject'
|
||||
|
||||
$scope.init = function (jobInformation) {
|
||||
$scope.progressValue = 0
|
||||
}
|
||||
|
||||
$scope.getProgress = function () {
|
||||
let statusList = _.pluck($scope.notebookJob.paragraphs, 'status')
|
||||
let runningJob = _.countBy(statusList, function (status) {
|
||||
if (status === ParagraphStatus.RUNNING || status === ParagraphStatus.FINISHED) {
|
||||
return 'matchCount'
|
||||
} else {
|
||||
return 'none'
|
||||
}
|
||||
})
|
||||
let totalCount = statusList.length
|
||||
let runningJobCount = runningJob.matchCount
|
||||
let result = Math.ceil(runningJobCount / totalCount * 100)
|
||||
return isNaN(result) ? 0 : result
|
||||
}
|
||||
|
||||
$scope.runNotebookJob = function (notebookId) {
|
||||
BootstrapDialog.confirm({
|
||||
closable: true,
|
||||
title: '',
|
||||
message: 'Run all paragraphs?',
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: baseUrlSrv.getRestApiBase() + '/notebook/job/' + notebookId,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}).then(function successCallback (response) {
|
||||
// success
|
||||
}, function errorCallback (errorResponse) {
|
||||
let errorText = 'SERVER ERROR'
|
||||
// eslint-disable-next-line no-extra-boolean-cast
|
||||
if (!!errorResponse.data.message) {
|
||||
errorText = errorResponse.data.message
|
||||
}
|
||||
BootstrapDialog.alert({
|
||||
closable: true,
|
||||
title: 'Execution Failure',
|
||||
message: errorText
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$scope.stopNotebookJob = function (notebookId) {
|
||||
BootstrapDialog.confirm({
|
||||
closable: true,
|
||||
title: '',
|
||||
message: 'Stop all paragraphs?',
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
$http({
|
||||
method: 'DELETE',
|
||||
url: baseUrlSrv.getRestApiBase() + '/notebook/job/' + notebookId,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}).then(function successCallback (response) {
|
||||
// success
|
||||
}, function errorCallback (errorResponse) {
|
||||
let errorText = 'SERVER ERROR'
|
||||
// eslint-disable-next-line no-extra-boolean-cast
|
||||
if (!!errorResponse.data.message) {
|
||||
errorText = errorResponse.data.message
|
||||
}
|
||||
BootstrapDialog.alert({
|
||||
closable: true,
|
||||
title: 'Stop Failure',
|
||||
message: errorText
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$scope.lastExecuteTime = function (unixtime) {
|
||||
return moment.unix(unixtime / 1000).fromNow()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<!--
|
||||
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="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">
|
||||
<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 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-progress-bar.html'"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span ng-repeat="paragraphJob in notebookJob.paragraphs">
|
||||
<a style="text-decoration: none !important;"
|
||||
ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}">
|
||||
<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>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -66,9 +66,9 @@ function WebsocketEventFactory ($rootScope, $websocket, $location, baseUrlSrv) {
|
|||
} else if (op === 'NOTES_INFO') {
|
||||
$rootScope.$broadcast('setNoteMenu', data.notes)
|
||||
} else if (op === 'LIST_NOTE_JOBS') {
|
||||
$rootScope.$broadcast('setNoteJobs', data.noteJobs)
|
||||
$rootScope.$emit('jobmanager:set-jobs', data.noteJobs)
|
||||
} else if (op === 'LIST_UPDATE_NOTE_JOBS') {
|
||||
$rootScope.$broadcast('setUpdateNoteJobs', data.noteRunningJobs)
|
||||
$rootScope.$emit('jobmanager:update-jobs', data.noteRunningJobs)
|
||||
} else if (op === 'AUTH_INFO') {
|
||||
let btn = []
|
||||
if ($rootScope.ticket.roles === '[]') {
|
||||
|
|
|
|||
|
|
@ -308,20 +308,20 @@ function WebsocketMessageService ($rootScope, websocketEvents) {
|
|||
return websocketEvents.isConnected()
|
||||
},
|
||||
|
||||
getNoteJobsList: function () {
|
||||
getJobs: function () {
|
||||
websocketEvents.sendNewEvent({op: 'LIST_NOTE_JOBS'})
|
||||
},
|
||||
|
||||
disconnectJobEvent: function () {
|
||||
websocketEvents.sendNewEvent({op: 'UNSUBSCRIBE_UPDATE_NOTE_JOBS'})
|
||||
},
|
||||
|
||||
getUpdateNoteJobsList: function (lastUpdateServerUnixTime) {
|
||||
websocketEvents.sendNewEvent(
|
||||
{op: 'LIST_UPDATE_NOTE_JOBS', data: {lastUpdateUnixTime: lastUpdateServerUnixTime * 1}}
|
||||
)
|
||||
},
|
||||
|
||||
unsubscribeJobManager: function () {
|
||||
websocketEvents.sendNewEvent({op: 'UNSUBSCRIBE_UPDATE_NOTE_JOBS'})
|
||||
},
|
||||
|
||||
getInterpreterBindings: function (noteId) {
|
||||
websocketEvents.sendNewEvent({op: 'GET_INTERPRETER_BINDINGS', data: {noteId: noteId}})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ limitations under the License.
|
|||
<link rel="stylesheet" href="app/notebook/paragraph/result/result.css" />
|
||||
<link rel="stylesheet" href="app/notebook/paragraph/result/display-table.css" />
|
||||
<link rel="stylesheet" href="app/jobmanager/jobmanager.css" />
|
||||
<link rel="stylesheet" href="app/jobmanager/jobs/job.css" />
|
||||
<link rel="stylesheet" href="app/interpreter/interpreter.css" />
|
||||
<link rel="stylesheet" href="app/helium/helium.css" />
|
||||
<link rel="stylesheet" href="app/credential/credential.css" />
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ import './app/visualization/builtins/visualization-areachart.js'
|
|||
import './app/visualization/builtins/visualization-linechart.js'
|
||||
import './app/visualization/builtins/visualization-scatterchart.js'
|
||||
|
||||
import './app/jobmanager/jobmanager.controller.js'
|
||||
import './app/jobmanager/jobs/job.controller.js'
|
||||
import './app/jobmanager/jobmanager.filter.js'
|
||||
import './app/jobmanager/jobmanager.component.js'
|
||||
import './app/interpreter/interpreter.controller.js'
|
||||
import './app/interpreter/interpreter.filter.js'
|
||||
import './app/interpreter/interpreter-item.directive.js'
|
||||
|
|
|
|||
|
|
@ -16,11 +16,8 @@
|
|||
*/
|
||||
package org.apache.zeppelin.helium;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.zeppelin.common.JsonSerializable;
|
||||
import org.apache.zeppelin.interpreter.Interpreter;
|
||||
import org.apache.zeppelin.notebook.Paragraph;
|
||||
import org.apache.zeppelin.resource.DistributedResourcePool;
|
||||
|
|
@ -38,10 +35,12 @@ import java.util.*;
|
|||
* Manages helium packages
|
||||
*/
|
||||
public class Helium {
|
||||
Logger logger = LoggerFactory.getLogger(Helium.class);
|
||||
private Logger logger = LoggerFactory.getLogger(Helium.class);
|
||||
private List<HeliumRegistry> registry = new LinkedList<>();
|
||||
|
||||
private final HeliumConf heliumConf;
|
||||
private HeliumConf heliumConf;
|
||||
private Map<String, List<HeliumPackageSearchResult>> allPackages = new HashMap<>();
|
||||
|
||||
private final String heliumConfPath;
|
||||
private final String registryPaths;
|
||||
private final File registryCacheDir;
|
||||
|
|
@ -49,8 +48,6 @@ public class Helium {
|
|||
private final HeliumBundleFactory bundleFactory;
|
||||
private final HeliumApplicationFactory applicationFactory;
|
||||
|
||||
Map<String, List<HeliumPackageSearchResult>> allPackages;
|
||||
|
||||
public Helium(
|
||||
String heliumConfPath,
|
||||
String registryPaths,
|
||||
|
|
@ -64,6 +61,7 @@ public class Helium {
|
|||
this.bundleFactory = bundleFactory;
|
||||
this.applicationFactory = applicationFactory;
|
||||
heliumConf = loadConf(heliumConfPath);
|
||||
allPackages = getAllPackageInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,24 +75,16 @@ public class Helium {
|
|||
}
|
||||
}
|
||||
|
||||
public List<HeliumRegistry> getAllRegistry() {
|
||||
synchronized (this.registry) {
|
||||
List list = new LinkedList<>();
|
||||
for (HeliumRegistry r : registry) {
|
||||
list.add(r);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
public void clear() {
|
||||
this.registry.clear();
|
||||
this.heliumConf = new HeliumConf();
|
||||
this.allPackages = new HashMap<>();
|
||||
}
|
||||
|
||||
public HeliumApplicationFactory getApplicationFactory() {
|
||||
return applicationFactory;
|
||||
}
|
||||
|
||||
public HeliumBundleFactory getBundleFactory() {
|
||||
return bundleFactory;
|
||||
}
|
||||
|
||||
private synchronized HeliumConf loadConf(String path) throws IOException {
|
||||
// add registry
|
||||
if (registryPaths != null && !registryPaths.isEmpty()) {
|
||||
|
|
@ -113,16 +103,14 @@ public class Helium {
|
|||
File heliumConfFile = new File(path);
|
||||
if (!heliumConfFile.isFile()) {
|
||||
logger.warn("{} does not exists", path);
|
||||
HeliumConf conf = new HeliumConf();
|
||||
return conf;
|
||||
return new HeliumConf();
|
||||
} else {
|
||||
String jsonString = FileUtils.readFileToString(heliumConfFile);
|
||||
HeliumConf conf = HeliumConf.fromJson(jsonString);
|
||||
return conf;
|
||||
return HeliumConf.fromJson(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save() throws IOException {
|
||||
public synchronized void saveConfig() throws IOException {
|
||||
String jsonString;
|
||||
synchronized (registry) {
|
||||
clearNotExistsPackages();
|
||||
|
|
@ -138,13 +126,11 @@ public class Helium {
|
|||
}
|
||||
|
||||
private void clearNotExistsPackages() {
|
||||
Map<String, List<HeliumPackageSearchResult>> all = getAllPackageInfoWithoutRefresh();
|
||||
|
||||
// clear visualization display order
|
||||
List<String> packageOrder = heliumConf.getBundleDisplayOrder();
|
||||
List<String> clearedOrder = new LinkedList<>();
|
||||
for (String pkgName : packageOrder) {
|
||||
if (all.containsKey(pkgName)) {
|
||||
if (allPackages.containsKey(pkgName)) {
|
||||
clearedOrder.add(pkgName);
|
||||
}
|
||||
}
|
||||
|
|
@ -153,16 +139,12 @@ public class Helium {
|
|||
// clear enabled package
|
||||
Map<String, String> enabledPackages = heliumConf.getEnabledPackages();
|
||||
for (String pkgName : enabledPackages.keySet()) {
|
||||
if (!all.containsKey(pkgName)) {
|
||||
if (!allPackages.containsKey(pkgName)) {
|
||||
heliumConf.disablePackage(pkgName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<HeliumPackageSearchResult>> getAllPackageInfoWithoutRefresh() {
|
||||
return getAllPackageInfo(false, null);
|
||||
}
|
||||
|
||||
public Map<String, List<HeliumPackageSearchResult>> getAllPackageInfo() {
|
||||
return getAllPackageInfo(true, null);
|
||||
}
|
||||
|
|
@ -176,8 +158,7 @@ public class Helium {
|
|||
Map<String, String> enabledPackageInfo = heliumConf.getEnabledPackages();
|
||||
|
||||
synchronized (registry) {
|
||||
if (refresh || allPackages == null) {
|
||||
allPackages = new HashMap<>();
|
||||
if (refresh || !allPackages.containsKey(packageName)) {
|
||||
for (HeliumRegistry r : registry) {
|
||||
try {
|
||||
for (HeliumPackage pkg : r.getAll()) {
|
||||
|
|
@ -188,12 +169,11 @@ public class Helium {
|
|||
continue;
|
||||
}
|
||||
|
||||
String artifact = enabledPackageInfo.get(name);
|
||||
boolean enabled = (artifact != null && artifact.equals(pkg.getArtifact()));
|
||||
|
||||
if (!allPackages.containsKey(name)) {
|
||||
allPackages.put(name, new LinkedList<HeliumPackageSearchResult>());
|
||||
if (allPackages.containsKey(name)) {
|
||||
allPackages.remove(name);
|
||||
}
|
||||
allPackages.put(name, new LinkedList<HeliumPackageSearchResult>());
|
||||
boolean enabled = enabledPackageInfo.containsKey(pkg.getName());
|
||||
allPackages.get(name).add(new HeliumPackageSearchResult(r.name(), pkg, enabled));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
@ -208,12 +188,10 @@ public class Helium {
|
|||
}
|
||||
|
||||
List<HeliumPackageSearchResult> pkgs = allPackages.get(name);
|
||||
String artifact = enabledPackageInfo.get(name);
|
||||
LinkedList<HeliumPackageSearchResult> newResults =
|
||||
new LinkedList<HeliumPackageSearchResult>();
|
||||
LinkedList<HeliumPackageSearchResult> newResults = new LinkedList<>();
|
||||
|
||||
for (HeliumPackageSearchResult pkg : pkgs) {
|
||||
boolean enabled = (artifact != null && artifact.equals(pkg.getPkg().getArtifact()));
|
||||
boolean enabled = enabledPackageInfo.containsKey(pkg.getPkg().getName());
|
||||
newResults.add(new HeliumPackageSearchResult(pkg.getRegistry(), pkg.getPkg(), enabled));
|
||||
}
|
||||
|
||||
|
|
@ -236,34 +214,31 @@ public class Helium {
|
|||
}
|
||||
|
||||
public List<HeliumPackageSearchResult> getAllEnabledPackages() {
|
||||
Map<String, List<HeliumPackageSearchResult>> allPackages = getAllPackageInfoWithoutRefresh();
|
||||
Map<String, String> enabledInfo = heliumConf.getEnabledPackages();
|
||||
List<HeliumPackageSearchResult> enabledPackages = new ArrayList<>();
|
||||
|
||||
for (List<HeliumPackageSearchResult> versionedPackages : allPackages.values()) {
|
||||
for (HeliumPackageSearchResult psr : versionedPackages) {
|
||||
if (psr.isEnabled()) {
|
||||
if (enabledInfo.containsKey(psr.getPkg().getName())) {
|
||||
enabledPackages.add(psr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enabledPackages;
|
||||
}
|
||||
|
||||
public List<HeliumPackageSearchResult> getSinglePackageInfo(String packageName) {
|
||||
Map<String, List<HeliumPackageSearchResult>> result = getAllPackageInfo(false, packageName);
|
||||
Map<String, List<HeliumPackageSearchResult>> result = getAllPackageInfo(true, packageName);
|
||||
|
||||
if (!result.containsKey(packageName)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return result.get(packageName);
|
||||
}
|
||||
|
||||
public HeliumPackageSearchResult getEnabledPackageInfo(String packageName) {
|
||||
Map<String, List<HeliumPackageSearchResult>> infos = getAllPackageInfoWithoutRefresh();
|
||||
List<HeliumPackageSearchResult> packages = infos.get(packageName);
|
||||
private HeliumPackageSearchResult getEnabledPackageInfo(String packageName) {
|
||||
List<HeliumPackageSearchResult> packages = allPackages.get(packageName);
|
||||
|
||||
for (HeliumPackageSearchResult pkgSearchResult : packages) {
|
||||
if (pkgSearchResult.isEnabled()) {
|
||||
|
|
@ -274,10 +249,10 @@ public class Helium {
|
|||
return null;
|
||||
}
|
||||
|
||||
public HeliumPackageSearchResult getPackageInfo(String pkgName, String artifact) {
|
||||
private HeliumPackageSearchResult getPackageInfo(String pkgName, String artifact) {
|
||||
Map<String, List<HeliumPackageSearchResult>> infos = getAllPackageInfo(false, pkgName);
|
||||
List<HeliumPackageSearchResult> packages = infos.get(pkgName);
|
||||
if (artifact == null) {
|
||||
if (StringUtils.isBlank(artifact)) {
|
||||
return packages.get(0); /** return the FIRST package */
|
||||
} else {
|
||||
for (HeliumPackageSearchResult pkg : packages) {
|
||||
|
|
@ -286,7 +261,6 @@ public class Helium {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -294,12 +268,12 @@ public class Helium {
|
|||
return bundleFactory.buildPackage(pkg, rebuild, true);
|
||||
}
|
||||
|
||||
public void enable(String name, String artifact) throws IOException {
|
||||
public boolean enable(String name, String artifact) throws IOException {
|
||||
HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact);
|
||||
|
||||
// no package found.
|
||||
if (pkgInfo == null) {
|
||||
return;
|
||||
logger.info("Package {} not found", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// if package is bundle, rebuild bundle
|
||||
|
|
@ -317,17 +291,19 @@ public class Helium {
|
|||
}
|
||||
}
|
||||
|
||||
save();
|
||||
saveConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disable(String name) throws IOException {
|
||||
String artifact = heliumConf.getEnabledPackages().get(name);
|
||||
public boolean disable(String name) throws IOException {
|
||||
String pkg = heliumConf.getEnabledPackages().get(name);
|
||||
|
||||
if (artifact == null) {
|
||||
return;
|
||||
if (pkg == null) {
|
||||
logger.info("Package {} not found", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
HeliumPackageSearchResult pkgInfo = getPackageInfo(name, artifact);
|
||||
HeliumPackageSearchResult pkgInfo = getPackageInfo(name, pkg);
|
||||
|
||||
// set `enable` field
|
||||
heliumConf.disablePackage(name);
|
||||
|
|
@ -337,21 +313,22 @@ public class Helium {
|
|||
currentDisplayOrder.remove(name);
|
||||
}
|
||||
}
|
||||
save();
|
||||
saveConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updatePackageConfig(String artifact, Map<String, Object> pkgConfig)
|
||||
throws IOException {
|
||||
|
||||
heliumConf.updatePackageConfig(artifact, pkgConfig);
|
||||
save();
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> getAllPackageConfig() {
|
||||
return heliumConf.getAllPackageConfigs();
|
||||
}
|
||||
|
||||
public Map<String, Object> getPackagePersistedConfig(String artifact) {
|
||||
private Map<String, Object> getPackagePersistedConfig(String artifact) {
|
||||
return heliumConf.getPackagePersistedConfig(artifact);
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +353,7 @@ public class Helium {
|
|||
allResources = ResourcePoolUtils.getAllResources();
|
||||
}
|
||||
|
||||
for (List<HeliumPackageSearchResult> pkgs : getAllPackageInfoWithoutRefresh().values()) {
|
||||
for (List<HeliumPackageSearchResult> pkgs : allPackages.values()) {
|
||||
for (HeliumPackageSearchResult pkg : pkgs) {
|
||||
if (pkg.getPkg().getType() == HeliumType.APPLICATION && pkg.isEnabled()) {
|
||||
ResourceSet resources = ApplicationLoader.findRequiredResourceSet(
|
||||
|
|
@ -404,10 +381,10 @@ public class Helium {
|
|||
* @return ordered list of enabled buildBundle package
|
||||
*/
|
||||
public List<HeliumPackage> getBundlePackagesToBundle() {
|
||||
Map<String, List<HeliumPackageSearchResult>> allPackages = getAllPackageInfoWithoutRefresh();
|
||||
List<String> visOrder = heliumConf.getBundleDisplayOrder();
|
||||
|
||||
List<HeliumPackage> orderedBundlePackages = new LinkedList<>();
|
||||
Set<HeliumPackage> orderedBundlePackages = new HashSet<>();
|
||||
List<HeliumPackage> output = new LinkedList<>();
|
||||
|
||||
// add enabled packages in visOrder
|
||||
for (String name : visOrder) {
|
||||
|
|
@ -418,7 +395,6 @@ public class Helium {
|
|||
for (HeliumPackageSearchResult pkgInfo : versions) {
|
||||
if (canBundle(pkgInfo)) {
|
||||
orderedBundlePackages.add(pkgInfo.getPkg());
|
||||
allPackages.remove(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -433,11 +409,11 @@ public class Helium {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orderedBundlePackages;
|
||||
new LinkedList<>().addAll(orderedBundlePackages);
|
||||
return output;
|
||||
}
|
||||
|
||||
public boolean canBundle(HeliumPackageSearchResult pkgInfo) {
|
||||
private boolean canBundle(HeliumPackageSearchResult pkgInfo) {
|
||||
return (pkgInfo.isEnabled() &&
|
||||
HeliumPackage.isBundleType(pkgInfo.getPkg().getType()));
|
||||
}
|
||||
|
|
@ -453,7 +429,7 @@ public class Helium {
|
|||
public void setVisualizationPackageOrder(List<String> orderedPackageList)
|
||||
throws IOException {
|
||||
heliumConf.setBundleDisplayOrder(orderedPackageList);
|
||||
save();
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -494,7 +470,7 @@ public class Helium {
|
|||
return createMixedConfig(configPersisted, configSpec);
|
||||
}
|
||||
|
||||
public static Map<String, Map<String, Object>> createMixedConfig(Map<String, Object> persisted,
|
||||
private static Map<String, Map<String, Object>> createMixedConfig(Map<String, Object> persisted,
|
||||
Map<String, Object> spec) {
|
||||
Map<String, Map<String, Object>> mixed = new HashMap<>();
|
||||
mixed.put("confPersisted", persisted);
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ public class HeliumBundleFactory {
|
|||
|
||||
String[] moduleNameVersion = getNpmModuleNameAndVersion(pkg);
|
||||
if (moduleNameVersion == null) {
|
||||
logger.error("Can't get module name and version of package " + pkg.getName());
|
||||
logger.warn("Can't get module name and version of package " + pkg.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,10 +47,6 @@ public class HeliumConf implements JsonSerializable {
|
|||
return new HashMap<>(enabled);
|
||||
}
|
||||
|
||||
public void enablePackage(HeliumPackage pkg) {
|
||||
enablePackage(pkg.getName(), pkg.getArtifact());
|
||||
}
|
||||
|
||||
public void enablePackage(String name, String artifact) {
|
||||
enabled.put(name, artifact);
|
||||
}
|
||||
|
|
@ -61,7 +57,6 @@ public class HeliumConf implements JsonSerializable {
|
|||
packageConfig.put(artifact,
|
||||
Collections.synchronizedMap(new HashMap<String, Object>()));
|
||||
}
|
||||
|
||||
packageConfig.put(artifact, newConfig);
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +86,7 @@ public class HeliumConf implements JsonSerializable {
|
|||
|
||||
public List<String> getBundleDisplayOrder() {
|
||||
if (bundleDisplayOrder == null) {
|
||||
return new LinkedList<String>();
|
||||
return new LinkedList<>();
|
||||
} else {
|
||||
return bundleDisplayOrder;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ import java.util.List;
|
|||
* Simple Helium registry on local filesystem
|
||||
*/
|
||||
public class HeliumLocalRegistry extends HeliumRegistry {
|
||||
Logger logger = LoggerFactory.getLogger(HeliumLocalRegistry.class);
|
||||
private Logger logger = LoggerFactory.getLogger(HeliumLocalRegistry.class);
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import java.util.UUID;
|
|||
* ]
|
||||
*/
|
||||
public class HeliumOnlineRegistry extends HeliumRegistry {
|
||||
Logger logger = LoggerFactory.getLogger(HeliumOnlineRegistry.class);
|
||||
private Logger logger = LoggerFactory.getLogger(HeliumOnlineRegistry.class);
|
||||
private final Gson gson;
|
||||
private final File registryCacheFile;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.zeppelin.interpreter.remote;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.zeppelin.display.AngularObject;
|
||||
import org.apache.zeppelin.display.AngularObjectRegistry;
|
||||
|
|
@ -140,15 +141,38 @@ public class RemoteInterpreter extends Interpreter {
|
|||
}
|
||||
|
||||
private Map<String, String> getEnvFromInterpreterProperty(Properties property) {
|
||||
Map<String, String> env = new HashMap<>();
|
||||
for (Object key : property.keySet()) {
|
||||
if (RemoteInterpreterUtils.isEnvString((String) key)) {
|
||||
env.put((String) key, property.getProperty((String) key));
|
||||
Map<String, String> env = new HashMap<String, String>();
|
||||
StringBuilder sparkConfBuilder = new StringBuilder();
|
||||
for (String key : property.stringPropertyNames()) {
|
||||
if (RemoteInterpreterUtils.isEnvString(key)) {
|
||||
env.put(key, property.getProperty(key));
|
||||
}
|
||||
if (key.equals("master")) {
|
||||
sparkConfBuilder.append(" --master " + property.getProperty("master"));
|
||||
}
|
||||
if (isSparkConf(key, property.getProperty(key))) {
|
||||
sparkConfBuilder.append(" --conf " + key + "=" +
|
||||
toShellFormat(property.getProperty(key)));
|
||||
}
|
||||
}
|
||||
env.put("ZEPPELIN_SPARK_CONF", sparkConfBuilder.toString());
|
||||
return env;
|
||||
}
|
||||
|
||||
private String toShellFormat(String value) {
|
||||
if (value.contains("\'") && value.contains("\"")) {
|
||||
throw new RuntimeException("Spark property value could not contain both \" and '");
|
||||
} else if (value.contains("\'")) {
|
||||
return "\"" + value + "\"";
|
||||
} else {
|
||||
return "\'" + value + "\'";
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isSparkConf(String key, String value) {
|
||||
return !StringUtils.isEmpty(key) && key.startsWith("spark.") && !StringUtils.isEmpty(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
|
|
@ -559,10 +583,6 @@ public class RemoteInterpreter extends Interpreter {
|
|||
return env;
|
||||
}
|
||||
|
||||
public void setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public void addEnv(Map<String, String> env) {
|
||||
if (this.env == null) {
|
||||
this.env = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import java.io.IOException;
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
|
|
@ -193,7 +196,16 @@ public class Authentication implements Runnable {
|
|||
}
|
||||
|
||||
private Key generateKey() {
|
||||
return new SecretKeySpec(toBytes(KEY), CIPHER_ALGORITHM);
|
||||
try {
|
||||
KeyGenerator kgen = KeyGenerator.getInstance(CIPHER_ALGORITHM);
|
||||
kgen.init(128, new SecureRandom(toBytes(KEY)));
|
||||
SecretKey secretKey = kgen.generateKey();
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
return new SecretKeySpec(enCodeFormat, CIPHER_ALGORITHM);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Cannot generate key for decryption", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] toBytes(String value) {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class HeliumTest {
|
|||
assertFalse(heliumConf.exists());
|
||||
|
||||
// when
|
||||
helium.save();
|
||||
helium.saveConfig();
|
||||
|
||||
// then
|
||||
assertTrue(heliumConf.exists());
|
||||
|
|
@ -101,7 +101,6 @@ public class HeliumTest {
|
|||
assertEquals(2, helium.getAllPackageInfo().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRefresh() throws IOException, URISyntaxException, TaskRunnerException {
|
||||
File heliumConf = new File(tmpDir, "helium.conf");
|
||||
|
|
@ -122,7 +121,7 @@ public class HeliumTest {
|
|||
""));
|
||||
|
||||
// then
|
||||
assertEquals(1, helium.getAllPackageInfoWithoutRefresh().size());
|
||||
assertEquals(1, helium.getAllPackageInfo().size());
|
||||
|
||||
// when
|
||||
registry1.add(new HeliumPackage(
|
||||
|
|
@ -136,7 +135,6 @@ public class HeliumTest {
|
|||
""));
|
||||
|
||||
// then
|
||||
assertEquals(1, helium.getAllPackageInfoWithoutRefresh().size());
|
||||
assertEquals(2, helium.getAllPackageInfo(true, null).size());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.io.IOException;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class HeliumTestApplication extends Application {
|
||||
AtomicInteger numRun = new AtomicInteger(0);
|
||||
private AtomicInteger numRun = new AtomicInteger(0);
|
||||
public HeliumTestApplication(ApplicationContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
|
||||
public class HeliumTestRegistry extends HeliumRegistry {
|
||||
List<HeliumPackage> infos = new LinkedList<>();
|
||||
private List<HeliumPackage> infos = new LinkedList<>();
|
||||
|
||||
public HeliumTestRegistry(String name, String uri) {
|
||||
super(name, uri);
|
||||
|
|
|
|||
Loading…
Reference in a new issue