Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2718

This commit is contained in:
tinkoff-dwh 2017-07-27 09:46:25 +03:00
commit 55b0f7b0ed
49 changed files with 1753 additions and 870 deletions

View file

@ -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"

View file

@ -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} `

View file

@ -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

View file

@ -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/>

View file

@ -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,

View file

@ -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));

View file

@ -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" +

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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 () {

View file

@ -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>

View 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')
})
})

View file

@ -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()

View file

@ -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>

View 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, })
}
})

View file

@ -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;">

View 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'
}
}

View 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

View 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',
}
}
})

View file

@ -80,6 +80,11 @@
z-index: 10003;
}
.job .control .job-control-btn {
cursor: pointer;
color: #3071A9;
}
@-webkit-keyframes spinnerRotateAnimation
{
from{-webkit-transform:rotate(0deg);}

View 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>

View 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)
}
}

View 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', },
])
})
})

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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="&#xf002 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"

View file

@ -0,0 +1,64 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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()
})
}
}

View 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',
}
}
})

View file

@ -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>

View file

@ -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>

View file

@ -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',
}

View file

@ -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()
}
}

View file

@ -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>

View file

@ -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 === '[]') {

View file

@ -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}})
},

View file

@ -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" />

View file

@ -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'

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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<>();

View file

@ -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) {

View file

@ -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());
}
}

View file

@ -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);
}

View file

@ -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);