diff --git a/.gitignore b/.gitignore index 5ad0d7aafb5..1c359d6ff0e 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ zeppelin-web/bower_components # R /r/lib/ .Rhistory +/R/ # project level /logs/ @@ -108,3 +109,6 @@ tramp # Generated by zeppelin-examples /helium + +# tmp files +/tmp/ diff --git a/.travis.yml b/.travis.yml index 6013bbf5f44..27a0e393cbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ cache: directories: - .spark-dist - ${HOME}/.m2/repository/.cache/maven-download-plugin + - .node_modules addons: apt: @@ -33,44 +34,49 @@ addons: matrix: include: - # Test all modules with spark-2.0.0-preview and scala 2.11 + # Test License compliance using RAT tool - jdk: "oraclejdk7" - env: SCALA_VER="2.11" SPARK_VER="2.0.0" HADOOP_VER="2.3" PROFILE="-Pspark-2.0 -Dspark.version=2.0.0-preview -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr" TEST_FLAG="verify -Pusing-packaged-distr" TEST_PROJECTS="" + env: SCALA_VER="2.11" SPARK_VER="2.0.0" HADOOP_VER="2.3" PROFILE="-Prat" BUILD_FLAG="clean" TEST_FLAG="org.apache.rat:apache-rat-plugin:check" TEST_PROJECTS="" + + # Test all modules with spark 2.0.0 and scala 2.11 + - jdk: "oraclejdk7" + env: SCALA_VER="2.11" SPARK_VER="2.0.0" HADOOP_VER="2.3" PROFILE="-Pspark-2.0 -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" # Test all modules with scala 2.10 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Pr -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.10" BUILD_FLAG="package -Pbuild-distr" TEST_FLAG="verify -Pusing-packaged-distr" TEST_PROJECTS="" + env: SCALA_VER="2.10" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Pr -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.10" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" # Test all modules with scala 2.11 - jdk: "oraclejdk7" - env: SCALA_VER="2.11" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Pr -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr" TEST_FLAG="verify -Pusing-packaged-distr" TEST_PROJECTS="" + env: SCALA_VER="2.11" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Pr -Phadoop-2.3 -Ppyspark -Psparkr -Pscalding -Pexamples -Pscala-2.11" BUILD_FLAG="package -Pbuild-distr -DskipRat" TEST_FLAG="verify -Pusing-packaged-distr -DskipRat" TEST_PROJECTS="" # Test spark module for 1.5.2 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.5.2" HADOOP_VER="2.3" PROFILE="-Pspark-1.5 -Pr -Phadoop-2.3 -Ppyspark -Psparkr" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,r -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" + env: SCALA_VER="2.10" SPARK_VER="1.5.2" HADOOP_VER="2.3" PROFILE="-Pspark-1.5 -Pr -Phadoop-2.3 -Ppyspark -Psparkr" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,r -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" # Test spark module for 1.4.1 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.4.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.4 -Pr -Phadoop-2.3 -Ppyspark -Psparkr" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,r -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" + env: SCALA_VER="2.10" SPARK_VER="1.4.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.4 -Pr -Phadoop-2.3 -Ppyspark -Psparkr" BUILD_FLAG="package -DskipTests -DskipRat" TEST_FLAG="verify -DskipRat" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark,r -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" # Test spark module for 1.3.1 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.3.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.3 -Phadoop-2.3 -Ppyspark" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" + env: SCALA_VER="2.10" SPARK_VER="1.3.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.3 -Phadoop-2.3 -Ppyspark" 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.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" # Test spark module for 1.2.2 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.2.2" HADOOP_VER="2.3" PROFILE="-Pspark-1.2 -Phadoop-2.3 -Ppyspark" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" + env: SCALA_VER="2.10" SPARK_VER="1.2.2" HADOOP_VER="2.3" PROFILE="-Pspark-1.2 -Phadoop-2.3 -Ppyspark" 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.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" # Test spark module for 1.1.1 - jdk: "oraclejdk7" - env: SCALA_VER="2.10" SPARK_VER="1.1.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.1 -Phadoop-2.3 -Ppyspark" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark -Dtest=org.apache.zeppelin.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" + env: SCALA_VER="2.10" SPARK_VER="1.1.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.1 -Phadoop-2.3 -Ppyspark" 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.rest.*Test,org.apache.zeppelin.spark* -DfailIfNoTests=false" # Test selenium with spark module for 1.6.1 - jdk: "oraclejdk7" - env: TEST_SELENIUM="true" SCALA_VER="2.10" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Phadoop-2.3 -Ppyspark -Pexamples" BUILD_FLAG="package -DskipTests" TEST_FLAG="verify" TEST_PROJECTS="-pl zeppelin-interpreter,zeppelin-zengine,zeppelin-server,zeppelin-display,spark-dependencies,spark -Dtest=org.apache.zeppelin.AbstractFunctionalSuite -DfailIfNoTests=false" + env: TEST_SELENIUM="true" SCALA_VER="2.10" SPARK_VER="1.6.1" HADOOP_VER="2.3" PROFILE="-Pspark-1.6 -Phadoop-2.3 -Ppyspark -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" before_install: - "ls -la .spark-dist ${HOME}/.m2/repository/.cache/maven-download-plugin" + - ls .node_modules && cp -r .node_modules zeppelin-web/node_modules || echo "node_modules are not cached" - mkdir -p ~/R - echo 'R_LIBS=~/R' > ~/.Renviron - R -e "install.packages('knitr', repos = 'http://cran.us.r-project.org', lib='~/R')" @@ -89,6 +95,7 @@ before_script: script: - mvn $TEST_FLAG $PROFILE -B $TEST_PROJECTS + - rm -rf .node_modules; cp -r zeppelin-web/node_modules .node_modules after_success: - echo "Travis exited with ${TRAVIS_TEST_RESULT}" @@ -104,3 +111,4 @@ after_failure: after_script: - ./testing/stopSparkCluster.sh $SPARK_VER $HADOOP_VER + diff --git a/LICENSE b/LICENSE index 8b209c53681..b243d2c68b5 100644 --- a/LICENSE +++ b/LICENSE @@ -242,7 +242,8 @@ The following components are provided under the MIT-style license. See project l The text of each license is also included at licenses/LICENSE-[project]-[version].txt. (MIT Style) jekyll-table-of-contents (https://github.com/ghiculescu/jekyll-table-of-contents) - https://github.com/ghiculescu/jekyll-table-of-contents/blob/master/LICENSE.txt - + (MIT Style) lunr.js (https://github.com/olivernn/lunr.js) - https://github.com/olivernn/lunr.js/blob/v0.7.1/LICENSE + ======================================================================== Apache licenses ======================================================================== diff --git a/README.md b/README.md index d702467d168..dc3460c28df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Zeppelin +# Apache Zeppelin **Documentation:** [User Guide](http://zeppelin.apache.org/docs/latest/index.html)
**Mailing Lists:** [User and Dev mailing list](http://zeppelin.apache.org/community.html)
@@ -93,9 +93,9 @@ _Notes:_ #### Install maven ``` -wget http://www.eu.apache.org/dist/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz -sudo tar -zxf apache-maven-3.3.3-bin.tar.gz -C /usr/local/ -sudo ln -s /usr/local/apache-maven-3.3.3/bin/mvn /usr/local/bin/mvn +wget http://www.eu.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz +sudo tar -zxf apache-maven-3.3.9-bin.tar.gz -C /usr/local/ +sudo ln -s /usr/local/apache-maven-3.3.9/bin/mvn /usr/local/bin/mvn ``` _Notes:_ @@ -217,6 +217,7 @@ Here're some examples: ```sh # build with spark-2.0, scala-2.11 +./dev/change_scala_version.sh 2.11 mvn clean package -Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pscala-2.11 # build with spark-1.6, scala-2.10 @@ -306,6 +307,7 @@ For configuration details check __`./conf`__ subdirectory. To produce a Zeppelin package compiled with Scala 2.11, use the -Pscala-2.11 profile: ``` +./dev/change_scala_version.sh 2.11 mvn clean package -Pspark-1.6 -Phadoop-2.4 -Pyarn -Ppyspark -Pscala-2.11 -DskipTests clean install ``` diff --git a/bin/common.cmd b/bin/common.cmd index c84f0778291..b4fb6bf4d23 100644 --- a/bin/common.cmd +++ b/bin/common.cmd @@ -81,13 +81,6 @@ if not defined JAVA_OPTS ( set JAVA_OPTS=%JAVA_OPTS% %ZEPPELIN_JAVA_OPTS% ) -if not defined ZEPPELIN_INTP_JAVA_OPTS ( - set ZEPPELIN_INTP_JAVA_OPTS=%ZEPPELIN_JAVA_OPTS% -) - -if not defined ZEPPELIN_INTP_MEM ( - set ZEPPELIN_INTP_MEM=%ZEPPELIN_MEM% -) set JAVA_INTP_OPTS=%ZEPPELIN_INTP_JAVA_OPTS% -Dfile.encoding=%ZEPPELIN_ENCODING% diff --git a/bin/common.sh b/bin/common.sh index 592aa1c89e8..b69f28cf0c7 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -121,15 +121,6 @@ JAVA_OPTS+=" ${ZEPPELIN_JAVA_OPTS} -Dfile.encoding=${ZEPPELIN_ENCODING} ${ZEPPEL JAVA_OPTS+=" -Dlog4j.configuration=file://${ZEPPELIN_CONF_DIR}/log4j.properties" export JAVA_OPTS -# jvm options for interpreter process -if [[ -z "${ZEPPELIN_INTP_JAVA_OPTS}" ]]; then - export ZEPPELIN_INTP_JAVA_OPTS="${ZEPPELIN_JAVA_OPTS}" -fi - -if [[ -z "${ZEPPELIN_INTP_MEM}" ]]; then - export ZEPPELIN_INTP_MEM="${ZEPPELIN_MEM}" -fi - JAVA_INTP_OPTS="${ZEPPELIN_INTP_JAVA_OPTS} -Dfile.encoding=${ZEPPELIN_ENCODING}" JAVA_INTP_OPTS+=" -Dlog4j.configuration=file://${ZEPPELIN_CONF_DIR}/log4j.properties" export JAVA_INTP_OPTS diff --git a/bin/interpreter.cmd b/bin/interpreter.cmd index 4a501f09ab2..fd6af3df224 100644 --- a/bin/interpreter.cmd +++ b/bin/interpreter.cmd @@ -46,6 +46,14 @@ if exist "%ZEPPELIN_HOME%\zeppelin-interpreter\target\classes" ( set ZEPPELIN_CLASSPATH=%ZEPPELIN_CLASSPATH%;"!ZEPPELIN_INTERPRETER_JAR!" ) +REM add test classes for unittest +if exist "%ZEPPELIN_HOME%\zeppelin-interpreter\target\test-classes" ( + set ZEPPELIN_CLASSPATH=%ZEPPELIN_CLASSPATH%;"%ZEPPELIN_HOME%\zeppelin-interpreter\target\test-classes" +) +if exist "%ZEPPELIN_HOME%\zeppelin-zengine\target\test-classes" ( + set ZEPPELIN_CLASSPATH=%ZEPPELIN_CLASSPATH%;"%ZEPPELIN_HOME%\zeppelin-zengine\target\test-classes" +) + call "%bin%\functions.cmd" ADDJARINDIR "%ZEPPELIN_HOME%\zeppelin-interpreter\target\lib" call "%bin%\functions.cmd" ADDJARINDIR "%INTERPRETER_DIR%" diff --git a/bin/interpreter.sh b/bin/interpreter.sh index 38d0f69e70d..a81c8f21067 100755 --- a/bin/interpreter.sh +++ b/bin/interpreter.sh @@ -63,6 +63,15 @@ else ZEPPELIN_INTP_CLASSPATH+=":${ZEPPELIN_INTERPRETER_JAR}" fi +# add test classes for unittest +if [[ -d "${ZEPPELIN_HOME}/zeppelin-interpreter/target/test-classes" ]]; then + ZEPPELIN_INTP_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-interpreter/target/test-classes" +fi +if [[ -d "${ZEPPELIN_HOME}/zeppelin-zengine/target/test-classes" ]]; then + ZEPPELIN_INTP_CLASSPATH+=":${ZEPPELIN_HOME}/zeppelin-zengine/target/test-classes" +fi + + addJarInDirForIntp "${ZEPPELIN_HOME}/zeppelin-interpreter/target/lib" addJarInDirForIntp "${INTERPRETER_DIR}" diff --git a/conf/shiro.ini b/conf/shiro.ini index ca5afe3471b..0562ba23bad 100644 --- a/conf/shiro.ini +++ b/conf/shiro.ini @@ -28,7 +28,10 @@ user3 = password4, role2 ### A sample for configuring Active Directory Realm #activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm #activeDirectoryRealm.systemUsername = userNameA + +#use either systemPassword or hadoopSecurityCredentialPath, more details in http://zeppelin.apache.org/docs/latest/security/shiroauthentication.html #activeDirectoryRealm.systemPassword = passwordA +#activeDirectoryRealm.hadoopSecurityCredentialPath = jceks://file/user/zeppelin/zeppelin.jceks #activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM #activeDirectoryRealm.url = ldap://ldap.test.com:389 #activeDirectoryRealm.groupRolesMap = "CN=admin,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"admin","CN=finance,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"finance","CN=hr,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"hr" @@ -42,6 +45,11 @@ user3 = password4, role2 #ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM #ldapRealm.contextFactory.authenticationMechanism = SIMPLE +### A sample for configuring ZeppelinHub Realm +#zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm +## Url of ZeppelinHub +#zeppelinHubRealm.zeppelinhubUrl = https://www.zeppelinhub.com +#securityManager.realms = $zeppelinHubRealm sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager diff --git a/conf/zeppelin-env.cmd.template b/conf/zeppelin-env.cmd.template index d85e59f2709..4d697fc3650 100644 --- a/conf/zeppelin-env.cmd.template +++ b/conf/zeppelin-env.cmd.template @@ -20,8 +20,8 @@ REM set JAVA_HOME= REM set MASTER= REM Spark master url. eg. spark://master_addr:7077. Leave empty if you want to use local mode. REM set ZEPPELIN_JAVA_OPTS REM Additional jvm options. for example, set ZEPPELIN_JAVA_OPTS="-Dspark.executor.memory=8g -Dspark.cores.max=16" REM set ZEPPELIN_MEM REM Zeppelin jvm mem options Default -Xmx1024m -XX:MaxPermSize=512m -REM set ZEPPELIN_INTP_MEM REM zeppelin interpreter process jvm mem options. Default = ZEPPELIN_MEM -REM set ZEPPELIN_INTP_JAVA_OPTS REM zeppelin interpreter process jvm options. Default = ZEPPELIN_JAVA_OPTS +REM set ZEPPELIN_INTP_MEM REM zeppelin interpreter process jvm mem options. +REM set ZEPPELIN_INTP_JAVA_OPTS REM zeppelin interpreter process jvm options. REM set ZEPPELIN_LOG_DIR REM Where log files are stored. PWD by default. REM set ZEPPELIN_PID_DIR REM The pid files are stored. /tmp by default. @@ -35,6 +35,7 @@ REM set ZEPPELIN_IDENT_STRING REM A string representing this instance of zep REM set ZEPPELIN_NICENESS REM The scheduling priority for daemons. Defaults to 0. REM set ZEPPELIN_INTERPRETER_LOCALREPO REM Local repository for interpreter's additional dependency loading REM set ZEPPELIN_NOTEBOOK_STORAGE REM Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote). +REM set ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC REM If there are multiple notebook storages, should we treat the first one as the only source of truth? REM Spark interpreter configuration @@ -62,7 +63,7 @@ REM REM set ZEPPELIN_SPARK_USEHIVECONTEXT REM Use HiveContext instead of SQLContext if set true. true by default. REM set ZEPPELIN_SPARK_CONCURRENTSQL REM Execute multiple SQL concurrently if set true. false by default. REM set ZEPPELIN_SPARK_IMPORTIMPLICIT REM Import implicits, UDF collection, and sql if set true. true by default. -REM set ZEPPELIN_SPARK_MAXRESULT REM Max number of SparkSQL result to display. 1000 by default. +REM set ZEPPELIN_SPARK_MAXRESULT REM Max number of Spark SQL result to display. 1000 by default. REM ZeppelinHub connection configuration REM diff --git a/conf/zeppelin-env.sh.template b/conf/zeppelin-env.sh.template index 52e36f7b5f6..01c6d538338 100644 --- a/conf/zeppelin-env.sh.template +++ b/conf/zeppelin-env.sh.template @@ -20,8 +20,8 @@ # export MASTER= # Spark master url. eg. spark://master_addr:7077. Leave empty if you want to use local mode. # export ZEPPELIN_JAVA_OPTS # Additional jvm options. for example, export ZEPPELIN_JAVA_OPTS="-Dspark.executor.memory=8g -Dspark.cores.max=16" # export ZEPPELIN_MEM # Zeppelin jvm mem options Default -Xmx1024m -XX:MaxPermSize=512m -# export ZEPPELIN_INTP_MEM # zeppelin interpreter process jvm mem options. Default = ZEPPELIN_MEM -# export ZEPPELIN_INTP_JAVA_OPTS # zeppelin interpreter process jvm options. Default = ZEPPELIN_JAVA_OPTS +# export ZEPPELIN_INTP_MEM # zeppelin interpreter process jvm mem options. +# export ZEPPELIN_INTP_JAVA_OPTS # zeppelin interpreter process jvm options. # export ZEPPELIN_LOG_DIR # Where log files are stored. PWD by default. # export ZEPPELIN_PID_DIR # The pid files are stored. ${ZEPPELIN_HOME}/run by default. @@ -36,6 +36,7 @@ # export ZEPPELIN_NICENESS # The scheduling priority for daemons. Defaults to 0. # export ZEPPELIN_INTERPRETER_LOCALREPO # Local repository for interpreter's additional dependency loading # export ZEPPELIN_NOTEBOOK_STORAGE # Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote). +# export ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC # If there are multiple notebook storages, should we treat the first one as the only source of truth? #### Spark interpreter configuration #### @@ -62,7 +63,7 @@ # export ZEPPELIN_SPARK_USEHIVECONTEXT # Use HiveContext instead of SQLContext if set true. true by default. # export ZEPPELIN_SPARK_CONCURRENTSQL # Execute multiple SQL concurrently if set true. false by default. # export ZEPPELIN_SPARK_IMPORTIMPLICIT # Import implicits, UDF collection, and sql if set true. true by default. -# export ZEPPELIN_SPARK_MAXRESULT # Max number of SparkSQL result to display. 1000 by default. +# export ZEPPELIN_SPARK_MAXRESULT # Max number of Spark SQL result to display. 1000 by default. # export ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE # Size in characters of the maximum text message to be received by websocket. Defaults to 1024000 diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template index 3bdc273f748..77e0b1f3bcd 100755 --- a/conf/zeppelin-site.xml.template +++ b/conf/zeppelin-site.xml.template @@ -164,6 +164,12 @@ notebook persistence layer implementation + + zeppelin.notebook.one.way.sync + false + If there are multiple notebook storages, should we treat the first one as the only source of truth? + + zeppelin.interpreter.dir interpreter @@ -184,7 +190,7 @@ zeppelin.interpreter.group.order - "spark,md,angular,sh,livy,alluxio,file,psql,flink,python,ignite,lens,cassandra,geode,kylin,elasticsearch,scalding,jdbc,hbase + spark,md,angular,sh,livy,alluxio,file,psql,flink,python,ignite,lens,cassandra,geode,kylin,elasticsearch,scalding,jdbc,hbase,bigquery diff --git a/dev/create_release.sh b/dev/create_release.sh index 60fc5bd9389..272713baa6b 100755 --- a/dev/create_release.sh +++ b/dev/create_release.sh @@ -66,6 +66,7 @@ function make_binary_release() { cp -r "${WORKING_DIR}/zeppelin" "${WORKING_DIR}/zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}" cd "${WORKING_DIR}/zeppelin-${RELEASE_VERSION}-bin-${BIN_RELEASE_NAME}" + ./dev/change_scala_version.sh 2.11 echo "mvn clean package -Pbuild-distr -DskipTests ${BUILD_FLAGS}" mvn clean package -Pbuild-distr -DskipTests ${BUILD_FLAGS} if [[ $? -ne 0 ]]; then @@ -102,8 +103,8 @@ function make_binary_release() { git_clone make_source_package -make_binary_release all "-Pspark-1.6 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr" -make_binary_release netinst "-Pspark-1.6 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr -pl !alluxio,!angular,!cassandra,!elasticsearch,!file,!flink,!hbase,!ignite,!jdbc,!kylin,!lens,!livy,!markdown,!postgresql,!python,!shell" +make_binary_release all "-Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr -Pscala-2.11" +make_binary_release netinst "-Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr -Pscala-2.11 -pl !alluxio,!angular,!cassandra,!elasticsearch,!file,!flink,!hbase,!ignite,!jdbc,!kylin,!lens,!livy,!markdown,!postgresql,!python,!shell,!bigquery" # remove non release files and dirs rm -rf "${WORKING_DIR}/zeppelin" diff --git a/dev/publish_release.sh b/dev/publish_release.sh index fc355d52b62..3a0a0f52f42 100755 --- a/dev/publish_release.sh +++ b/dev/publish_release.sh @@ -44,7 +44,7 @@ NC='\033[0m' # No Color RELEASE_VERSION="$1" GIT_TAG="$2" -PUBLISH_PROFILES="-Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr" +PUBLISH_PROFILES="-Pbuild-distr -Pspark-2.0 -Phadoop-2.4 -Pyarn -Ppyspark -Psparkr -Pr" PROJECT_OPTIONS="-pl !zeppelin-distribution" NEXUS_STAGING="https://repository.apache.org/service/local/staging" NEXUS_PROFILE="153446d1ac37c4" @@ -93,10 +93,10 @@ function publish_to_maven() { tmp_repo="$(mktemp -d /tmp/zeppelin-repo-XXXXX)" # build with scala-2.10 - echo "mvn clean install -Ppublish-distr \ + echo "mvn clean install -DskipTests \ -Dmaven.repo.local=${tmp_repo} -Pscala-2.10 \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS}" - mvn clean install -Ppublish-distr -Dmaven.repo.local="${tmp_repo}" -Pscala-2.10 \ + mvn clean install -DskipTests -Dmaven.repo.local="${tmp_repo}" -Pscala-2.10 \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS} if [[ $? -ne 0 ]]; then echo "Build with scala 2.10 failed." @@ -106,10 +106,10 @@ function publish_to_maven() { # build with scala-2.11 "${BASEDIR}/change_scala_version.sh" 2.11 - echo "mvn clean install -Ppublish-distr \ + echo "mvn clean install -DskipTests \ -Dmaven.repo.local=${tmp_repo} -Pscala-2.11 \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS}" - mvn clean install -Ppublish-distr -Dmaven.repo.local="${tmp_repo}" -Pscala-2.11 \ + mvn clean install -DskipTests -Dmaven.repo.local="${tmp_repo}" -Pscala-2.11 \ ${PUBLISH_PROFILES} ${PROJECT_OPTIONS} if [[ $? -ne 0 ]]; then echo "Build with scala 2.11 failed." diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000000..7cdb8adc8b6 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,94 @@ +# Contributing to Apache Zeppelin Documentation + +## Folder Structure +`docs/` folder is organized as below: + +``` +docs/ + ├── _includes/themes/zeppelin + │ ├── _navigation.html + │ └── default.html + ├── _layouts + ├── _plugins + ├── assets/themes/zeppelin -> {ASSET_PATH} + │ ├── bootstrap + │ ├── css + │ ├── img + │ └── js + ├── development/ *.md + ├── displaysystem/ *.md + ├── install/ *.md + ├── interpreter/ *.md + ├── manual/ *.md + ├── quickstart/ *.md + ├── rest-api/ *.md + ├── security/ *.md + ├── storage/ *.md + ├── Gemfile + ├── Gemfile.lock + ├── _config.yml + ├── index.md + └── ... +``` + + - `_navigation.html`: the dropdown menu in navbar + - `default.html` & `_layouts/`: define default HTML layout + - `_plugins/`: custom plugin `*.rb` files can be placed in this folder. See [jekyll/plugins](https://jekyllrb.com/docs/plugins/) for the further information. + - `{ASSET_PATH}/css/style.css`: extra css components can be defined + - `{ASSET_PATH}/img/docs-img/`: image files used for document pages can be placed in this folder + - `{ASSET_PATH}/js/`: extra `.js` files can be placed + - `Gemfile`: defines bundle dependencies. They will be installed by `bundle install`. + - `Gemfile.lock`: when you run `bundle install`, bundler will persist all gems name and their version to this file. For the more details, see [Bundle "The Gemfile Lock"](http://bundler.io/v1.10/man/bundle-install.1.html#THE-GEMFILE-LOCK) + - `documentation_group`: `development/`, `displaysystem/`, `install/`, `interpreter/`... + - `_config.yml`: defines configuration options for docs website. See [jekyll/configuration](https://jekyllrb.com/docs/configuration/) for the other available config variables. + - `index.md`: the main page of `http://zeppelin.apache.org/docs//` + + +## Markdown +Zeppelin documentation pages are written with [Markdown](http://daringfireball.net/projects/markdown/). It is possible to use [GitHub flavored syntax](https://help.github.com/categories/writing-on-github/) and intermix plain HTML. + +## Front matter +Every page contains [YAML front matter](https://jekyllrb.com/docs/frontmatter/) block in their header. Don't forget to wrap the front matter list with triple-dashed lines(`---`) like below. +The document page should start this triple-dashed lines. Or you will face 404 error, since Jekyll can't find the page. + +``` +--- +layout: page +title: "Apache Zeppelin Tutorial" +description: "This tutorial page contains a short walk-through tutorial that uses Apache Spark backend. Please note that this tutorial is valid for Spark 1.3 and higher." +group: quickstart +--- +``` + + - `layout`: the default layout is `page` which is defined in `_layout/page.html`. + - `title`: the title for the document. Please note that if it needs to include `Zeppelin`, it should be `Apache Zeppelin`, not `Zeppelin`. + - `description`: a short description for the document. One or two sentences would be enough. This description also will be shown as an extract sentence when people search pages. + - `group`: a category of the document page + +## Headings +All documents are structured with headings. From these headings, you can automatically generate a **Table of Contents**. There is a simple rule for Zeppelin docs headings. + +``` +# Level-1 heading <- used only for the main title +## Level-2 heading <- start with this +### Level-3 heading +#### Level-4 heading <- won't be converted in TOC from this level +``` + +## Table of contents(TOC) + +``` +
+``` + +Add this line below `# main title` in order to generate a **Table of Contents**. Headings until `### (Level-3 heading)` are included to TOC. + + +Default setting options for TOC are definded in [here](https://github.com/apache/zeppelin/blob/master/docs/assets/themes/zeppelin/js/toc.js#L4). + + +## Adding new pages +If you're going to create new pages, there are some spots you need to add the location of the page. + + - **Dropdown menu in navbar**: add your docs location to [_navigation.html](https://github.com/apache/zeppelin/blob/master/docs/_includes/themes/zeppelin/_navigation.html) + - **Main index**: add your docs below [What is the next?](http://zeppelin.apache.org/docs/latest/#what-is-the-next) section in [index.md](https://github.com/apache/zeppelin/blob/master/docs/index.md) with a short description. No need to do this if the page is for **Interpreters**. diff --git a/docs/README.md b/docs/README.md index 6e5e716f216..fdf27fa6b8f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,41 +1,55 @@ -## Apache Zeppelin documentation - -This readme will walk you through building the Zeppelin documentation, which is included here with the Zeppelin source code. +# Apache Zeppelin documentation +This README will walk you through building the documentation of Apache Zeppelin. The documentation is included here with Apache Zeppelin source code. The online documentation at [https://zeppelin.apache.org/docs/](https://zeppelin.apache.org/docs/latest) is also generated from the files found in here. ## Build documentation -See https://help.github.com/articles/using-jekyll-with-pages#installing-jekyll +Zeppelin is using [Jekyll](https://jekyllrb.com/) which is a static site generator and [Github Pages](https://pages.github.com/) as a site publisher. For the more details, see [help.github.com/articles/about-github-pages-and-jekyll/](https://help.github.com/articles/about-github-pages-and-jekyll/). **Requirements** ``` - ruby --version >= 2.0.0 - gem install bundler - # go to /docs under your Zeppelin source - bundle install +# ruby --version >= 2.0.0 +# Install Bundler using gem +gem install bundler + +cd $ZEPPELIN_HOME/docs +# Install all dependencies declared in the Gemfile +bundle install ``` -For the further information about requirements, please see [here](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements). +For the further information about requirements, please see [here](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements). + +On OS X 10.9, you may need to do + +``` +xcode-select --install +``` -*On OS X 10.9 you may need to do "xcode-select --install"* +## Run website locally +If you don't want to encounter uglily rendered pages, run the documentation site in your local first. +In `$ZEPPELIN_HOME/docs`, -## Run website +``` +bundle exec jekyll serve --watch +``` - bundle exec jekyll serve --watch +Using the above command, Jekyll will start a web server at `http://localhost:4000` and watch the `/docs` directory to update. -## Adding a new page - rake page name="new-page.md" +## Contribute to Zeppelin documentation +If you wish to help us and contribute to Zeppelin Documentation, please look at [Zeppelin Documentation's contribution guideline](https://github.com/apache/zeppelin/blob/master/docs/CONTRIBUTING.md). -## Bumping up version in a new release +## For committers only +### Bumping up version in a new release * `ZEPPELIN_VERSION` and `BASE_PATH` property in _config.yml -## Deploy to ASF svnpubsub infra (for committers only) +### Deploy to ASF svnpubsub infra 1. generate static website in `./_site` + ``` # go to /docs under Zeppelin source bundle exec jekyll build --safe diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html index 7756f233820..9bd9967244a 100644 --- a/docs/_includes/themes/zeppelin/_navigation.html +++ b/docs/_includes/themes/zeppelin/_navigation.html @@ -32,7 +32,6 @@
  • Customize Zeppelin Homepage
  • More
  • -
  • Zeppelin on Vagrant VM
  • Upgrade Zeppelin Version
  • @@ -103,6 +102,12 @@
  • Notebook Authorization
  • Data Source Authorization
  • +
  • Advanced
  • +
  • Zeppelin on Vagrant VM
  • +
  • Zeppelin on Spark Cluster Mode (Standalone)
  • +
  • Zeppelin on Spark Cluster Mode (YARN)
  • +
  • Zeppelin on Spark Cluster Mode (Mesos)
  • +
  • Contibute
  • Writing Zeppelin Interpreter
  • Writing Zeppelin Application (Experimental)
  • @@ -111,7 +116,14 @@ + - + diff --git a/docs/_includes/themes/zeppelin/default.html b/docs/_includes/themes/zeppelin/default.html index cd07602ec90..8d84124bb1e 100644 --- a/docs/_includes/themes/zeppelin/default.html +++ b/docs/_includes/themes/zeppelin/default.html @@ -2,7 +2,7 @@ - {{ page.title }} + Apache Zeppelin {{ site.ZEPPELIN_VERSION }} Documentation: {{ page.title }} {% if page.description %}{% endif %} @@ -34,6 +34,8 @@ + + diff --git a/docs/assets/themes/zeppelin/css/style.css b/docs/assets/themes/zeppelin/css/style.css index 1a6f3da976a..98b3895e5eb 100644 --- a/docs/assets/themes/zeppelin/css/style.css +++ b/docs/assets/themes/zeppelin/css/style.css @@ -438,11 +438,10 @@ a.anchor { .content table { display: block; width: 100%; - overflow: auto; word-break: normal; word-break: keep-all; -webkit-overflow-scrolling: touch; - font-size: 90%; + font-size: 87%; margin-top: 16px; margin-bottom: 16px; } @@ -569,6 +568,30 @@ a.anchorjs-link:hover { text-decoration: none; } margin-left: -18px; } +/* Search Page */ +#search p { + font-size: 30px; + font-weight: bold; + color: black; +} + +#search_results p { + font-size: 13px; + font-weight: 400; +} + +#search_results a { + vertical-align: super; + font-size: 16px; + text-decoration: underline; +} + +#search_results .link { + font-size: 13px; + color: #008000; + padding-bottom: 3px; +} + /* Custom, iPhone Retina */ @media only screen and (max-width: 480px) { .jumbotron h1 { diff --git a/docs/assets/themes/zeppelin/img/available_interpreters.png b/docs/assets/themes/zeppelin/img/available_interpreters.png index dc5545e250a..cb1a384c766 100644 Binary files a/docs/assets/themes/zeppelin/img/available_interpreters.png and b/docs/assets/themes/zeppelin/img/available_interpreters.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/mesos_frameworks.png b/docs/assets/themes/zeppelin/img/docs-img/mesos_frameworks.png new file mode 100644 index 00000000000..af428930fd6 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/mesos_frameworks.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/spark_ui.png b/docs/assets/themes/zeppelin/img/docs-img/spark_ui.png new file mode 100644 index 00000000000..ca91cf02432 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/spark_ui.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/standalone_conf.png b/docs/assets/themes/zeppelin/img/docs-img/standalone_conf.png new file mode 100644 index 00000000000..908fc84fbf7 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/standalone_conf.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/yarn_applications.png b/docs/assets/themes/zeppelin/img/docs-img/yarn_applications.png new file mode 100644 index 00000000000..06c5296ad63 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/yarn_applications.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/zeppelin_mesos_conf.png b/docs/assets/themes/zeppelin/img/docs-img/zeppelin_mesos_conf.png new file mode 100644 index 00000000000..b85a3da4744 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/zeppelin_mesos_conf.png differ diff --git a/docs/assets/themes/zeppelin/img/docs-img/zeppelin_yarn_conf.png b/docs/assets/themes/zeppelin/img/docs-img/zeppelin_yarn_conf.png new file mode 100644 index 00000000000..435193ac15e Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/zeppelin_yarn_conf.png differ diff --git a/docs/assets/themes/zeppelin/img/spark_logo.jpg b/docs/assets/themes/zeppelin/img/spark_logo.jpg deleted file mode 100644 index a13c87019c1..00000000000 Binary files a/docs/assets/themes/zeppelin/img/spark_logo.jpg and /dev/null differ diff --git a/docs/assets/themes/zeppelin/img/spark_logo.png b/docs/assets/themes/zeppelin/img/spark_logo.png new file mode 100644 index 00000000000..afe2d1baeb8 Binary files /dev/null and b/docs/assets/themes/zeppelin/img/spark_logo.png differ diff --git a/docs/assets/themes/zeppelin/js/lunr.min.js b/docs/assets/themes/zeppelin/js/lunr.min.js new file mode 100644 index 00000000000..b0198dff91f --- /dev/null +++ b/docs/assets/themes/zeppelin/js/lunr.min.js @@ -0,0 +1,7 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.7.0 + * Copyright (C) 2016 Oliver Nightingale + * MIT Licensed + * @license + */ +!function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.7.0",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.utils.asString=function(t){return void 0===t||null===t?"":t.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(e){return arguments.length&&null!=e&&void 0!=e?Array.isArray(e)?e.map(function(e){return t.utils.asString(e).toLowerCase()}):e.toString().trim().toLowerCase().split(t.tokenizer.seperator):[]},t.tokenizer.seperator=/[\s\-]+/,t.tokenizer.load=function(t){var e=this.registeredFunctions[t];if(!e)throw new Error("Cannot load un-registered function: "+t);return e},t.tokenizer.label="default",t.tokenizer.registeredFunctions={"default":t.tokenizer},t.tokenizer.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing tokenizer: "+n),e.label=n,this.registeredFunctions[n]=e},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,r=0;n>r;r++){for(var o=t[r],s=0;i>s&&(o=this._stack[s](o,r,t),void 0!==o&&""!==o);s++);void 0!==o&&""!==o&&e.push(o)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(o===t)return r;t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r]}return o===t?r:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,r=e+Math.floor(i/2),o=this.elements[r];i>1;)t>o&&(e=r),o>t&&(n=r),i=n-e,r=e+Math.floor(i/2),o=this.elements[r];return o>t?r:t>o?r+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,r=0,o=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>o-1||r>s-1)break;a[i]!==h[r]?a[i]h[r]&&r++:(n.add(a[i]),i++,r++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone();for(var r=0,o=n.toArray();rp;p++)c[p]===a&&d++;h+=d/f*l.boost}}this.tokenStore.add(a,{ref:o,tf:h})}n&&this.eventEmitter.emit("add",e,this)},t.Index.prototype.remove=function(t,e){var n=t[this._ref],e=void 0===e?!0:e;if(this.documentStore.has(n)){var i=this.documentStore.get(n);this.documentStore.remove(n),i.forEach(function(t){this.tokenStore.remove(t,n)},this),e&&this.eventEmitter.emit("remove",t,this)}},t.Index.prototype.update=function(t,e){var e=void 0===e?!0:e;this.remove(t,!1),this.add(t,!1),e&&this.eventEmitter.emit("update",t,this)},t.Index.prototype.idf=function(t){var e="@"+t;if(Object.prototype.hasOwnProperty.call(this._idfCache,e))return this._idfCache[e];var n=this.tokenStore.count(t),i=1;return n>0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(this.tokenizerFn(e)),i=new t.Vector,r=[],o=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*o,h=this,u=this.tokenStore.expand(e).reduce(function(n,r){var o=h.corpusTokens.indexOf(r),s=h.idf(r),u=1,l=new t.SortedSet;if(r!==e){var c=Math.max(3,r.length-e.length);u=1/Math.log(c)}o>-1&&i.insert(o,a*s*u);for(var f=h.tokenStore.get(r),d=Object.keys(f),p=d.length,v=0;p>v;v++)l.add(f[d[v]].ref);return n.union(l)},new t.SortedSet);r.push(u)},this);var a=r.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,r=new t.Vector,o=0;i>o;o++){var s=n.elements[o],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);r.insert(this.corpusTokens.indexOf(s),a*h)}return r},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,tokenizer:this.tokenizerFn.label,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",r=n+"[^aeiouy]*",o=i+"[aeiou]*",s="^("+r+")?"+o+r,a="^("+r+")?"+o+r+"("+o+")?$",h="^("+r+")?"+o+r+o+r,u="^("+r+")?"+i,l=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(u),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),k=new RegExp("^"+r+i+"[^aeiouwxy]$"),x=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,F=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,_=/^(.+?)(s|t)(ion)$/,z=/^(.+?)e$/,O=/ll$/,P=new RegExp("^"+r+i+"[^aeiouwxy]$"),T=function(n){var i,r,o,s,a,h,u;if(n.length<3)return n;if(o=n.substr(0,1),"y"==o&&(n=o.toUpperCase()+n.substr(1)),s=p,a=v,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=g,a=m,s.test(n)){var T=s.exec(n);s=l,s.test(T[1])&&(s=y,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,u=k,a.test(n)?n+="e":h.test(n)?(s=y,n=n.replace(s,"")):u.test(n)&&(n+="e"))}if(s=x,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+t[r])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],r=T[2],s=l,s.test(i)&&(n=i+e[r])}if(s=F,a=_,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=z,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=P,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=O,a=c,s.test(n)&&a.test(n)&&(s=y,n=n.replace(s,"")),"y"==o&&(n=o.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.generateStopWordFilter=function(t){var e=t.reduce(function(t,e){return t[e]=e,t},{});return function(t){return t&&e[t]!==t?t:void 0}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){return t.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t.charAt(0),r=t.slice(1);return i in n||(n[i]={docs:{}}),0===r.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(r,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;nFound '+results.length+' result(s)


    '); + + results.forEach(function(result) { + var item = loaded_data[result.ref]; + var appendString = ''+item.title+'

    '+item.excerpt+'


    '; + + $search_results.append(appendString); + }); + } else { + $search_results.html('

    Your search did not match any documents.
    Make sure that all words are spelled correctly or try more general keywords.

    '); + } + }); + } +}); diff --git a/docs/development/howtocontribute.md b/docs/development/howtocontribute.md index 2d3842a2257..cd0ca3f6f0a 100644 --- a/docs/development/howtocontribute.md +++ b/docs/development/howtocontribute.md @@ -1,9 +1,23 @@ --- layout: page -title: "How to contribute" -description: "How to contribute" +title: "Contributing to Apache Zeppelin (Code)" +description: "How can you contribute to Apache Zeppelin project? This document covers from setting up your develop environment to making a pull request on Github." group: development --- + +{% include JB/setup %} # Contributing to Apache Zeppelin ( Code ) diff --git a/docs/development/howtocontributewebsite.md b/docs/development/howtocontributewebsite.md index 0db15551585..2ee299b40aa 100644 --- a/docs/development/howtocontributewebsite.md +++ b/docs/development/howtocontributewebsite.md @@ -1,9 +1,23 @@ --- layout: page -title: "How to contribute (website)" -description: "How to contribute (website)" +title: "Contributing to Apache Zeppelin (Website)" +description: "How can you contribute to Apache Zeppelin project website? This document covers from building Zeppelin documentation site to making a pull request on Github." group: development --- + +{% include JB/setup %} # Contributing to Apache Zeppelin ( Website ) diff --git a/docs/development/writingzeppelinapplication.md b/docs/development/writingzeppelinapplication.md index c00aabe563f..e99bbcbe52d 100644 --- a/docs/development/writingzeppelinapplication.md +++ b/docs/development/writingzeppelinapplication.md @@ -1,7 +1,7 @@ --- layout: page -title: "Writing Zeppelin Application" -description: "" +title: "Writing a new Application(Experimental)" +description: "Apache Zeppelin Application is a package that runs on Interpreter process and displays it's output inside of the notebook. Make your own Application in Apache Zeppelin is quite easy." group: development --- +{% include JB/setup %} + +# Apache Zeppelin on Spark Cluster Mode + +
    + +## Overview +[Apache Spark](http://spark.apache.org/) has supported three cluster manager types([Standalone](http://spark.apache.org/docs/latest/spark-standalone.html), [Apache Mesos](http://spark.apache.org/docs/latest/running-on-mesos.html) and [Hadoop YARN](http://spark.apache.org/docs/latest/running-on-yarn.html)) so far. +This document will guide you how you can build and configure the environment on 3 types of Spark cluster manager with Apache Zeppelin using [Docker](https://www.docker.com/) scripts. +So [install docker](https://docs.docker.com/engine/installation/) on the machine first. + +## Spark standalone mode +[Spark standalone](http://spark.apache.org/docs/latest/spark-standalone.html) is a simple cluster manager included with Spark that makes it easy to set up a cluster. +You can simply set up Spark standalone environment with below steps. + +> **Note :** Since Apache Zeppelin and Spark use same `8080` port for their web UI, you might need to change `zeppelin.server.port` in `conf/zeppelin-site.xml`. + +### 1. Build Docker file +You can find docker script files under `scripts/docker/spark-cluster-managers`. + +``` +cd $ZEPPELIN_HOME/scripts/docker/spark-cluster-managers/spark_standalone +docker build -t "spark_standalone" . +``` + +### 2. Run docker + +``` +docker run -it \ +-p 8080:8080 \ +-p 7077:7077 \ +-p 8888:8888 \ +-p 8081:8081 \ +-h sparkmaster \ +--name spark_standalone \ +spark_standalone bash; +``` + +### 3. Configure Spark interpreter in Zeppelin +Set Spark master as `spark://:7077` in Zeppelin **Interpreters** setting page. + + + +### 4. Run Zeppelin with Spark interpreter +After running single paragraph with Spark interpreter in Zeppelin, browse `https://:8080` and check whether Spark cluster is running well or not. + + + +You can also simply verify that Spark is running well in Docker with below command. + +``` +ps -ef | grep spark +``` + + +## Spark on YARN mode +You can simply set up [Spark on YARN](http://spark.apache.org/docs/latest/running-on-yarn.html) docker environment with below steps. + +> **Note :** Since Apache Zeppelin and Spark use same `8080` port for their web UI, you might need to change `zeppelin.server.port` in `conf/zeppelin-site.xml`. + +### 1. Build Docker file +You can find docker script files under `scripts/docker/spark-cluster-managers`. + +``` +cd $ZEPPELIN_HOME/scripts/docker/spark-cluster-managers/spark_yarn +docker build -t "spark_yarn" . +``` + +### 2. Run docker + +``` +docker run -it \ + -p 5000:5000 \ + -p 9000:9000 \ + -p 9001:9001 \ + -p 8088:8088 \ + -p 8042:8042 \ + -p 8030:8030 \ + -p 8031:8031 \ + -p 8032:8032 \ + -p 8033:8033 \ + -p 8080:8080 \ + -p 7077:7077 \ + -p 8888:8888 \ + -p 8081:8081 \ + -p 50010:50010 \ + -p 50075:50075 \ + -p 50020:50020 \ + -p 50070:50070 \ + --name spark_yarn \ + -h sparkmaster \ + spark_yarn bash; +``` + +### 3. Verify running Spark on YARN. + +You can simply verify the processes of Spark and YARN are running well in Docker with below command. + +``` +ps -ef +``` + +You can also check each application web UI for HDFS on `http://:50070/`, YARN on `http://:8088/cluster` and Spark on `http://:8080/`. + +### 4. Configure Spark interpreter in Zeppelin +Set following configurations to `conf/zeppelin-env.sh`. + +``` +export MASTER=yarn-client +export HADOOP_CONF_DIR=[your_hadoop_conf_path] +export SPARK_HOME=[your_spark_home_path] +``` + +`HADOOP_CONF_DIR`(Hadoop configuration path) is defined in `/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf`. + +Don't forget to set Spark `master` as `yarn-client` in Zeppelin **Interpreters** setting page like below. + + + +### 5. Run Zeppelin with Spark interpreter +After running a single paragraph with Spark interpreter in Zeppelin, browse `http://:8088/cluster/apps` and check Zeppelin application is running well or not. + + + + + +## Spark on Mesos mode +You can simply set up [Spark on Mesos](http://spark.apache.org/docs/latest/running-on-mesos.html) docker environment with below steps. + + +### 1. Build Docker file + +``` +cd $ZEPPELIN_HOME/scripts/docker/spark-cluster-managers/spark_mesos +docker build -t "spark_mesos" . +``` + + +### 2. Run docker + +``` +docker run --net=host -it \ +-p 8080:8080 \ +-p 7077:7077 \ +-p 8888:8888 \ +-p 8081:8081 \ +-p 8082:8082 \ +-p 5050:5050 \ +-p 5051:5051 \ +-p 4040:4040 \ +-h sparkmaster \ +--name spark_mesos \ +spark_mesos bash; +``` + +### 3. Verify running Spark on Mesos. + +You can simply verify the processes of Spark and Mesos are running well in Docker with below command. + +``` +ps -ef +``` + +You can also check each application web UI for Mesos on `http://:5050/cluster` and Spark on `http://:8080/`. + + +### 4. Configure Spark interpreter in Zeppelin + +``` +export MASTER=mesos://127.0.1.1:5050 +export MESOS_NATIVE_JAVA_LIBRARY=[PATH OF libmesos.so] +export SPARK_HOME=[PATH OF SPARK HOME] +``` + + +Don't forget to set Spark `master` as `mesos://127.0.1.1:5050` in Zeppelin **Interpreters** setting page like below. + + + + +### 5. Run Zeppelin with Spark interpreter +After running a single paragraph with Spark interpreter in Zeppelin, browse `http://:5050/#/frameworks` and check Zeppelin application is running well or not. + + + diff --git a/docs/install/upgrade.md b/docs/install/upgrade.md index a831eb98d4e..60a8f885a5b 100644 --- a/docs/install/upgrade.md +++ b/docs/install/upgrade.md @@ -1,7 +1,7 @@ --- layout: page -title: "Manual upgrade procedure for Zeppelin" -description: "" +title: "Manual Zeppelin version upgrade procedure" +description: "This document will guide you through a procedure of manual upgrade your Apache Zeppelin instance to a newer version. Apache Zeppelin keeps backward compatibility for the notebook file format." group: install --- {% include JB/setup %} -# Vagrant Virtual Machine for Apache Zeppelin +# Apache Zeppelin on Vagrant Virtual Machine
    ## Overview -Apache Zeppelin distribution includes a scripts directory +Apache Zeppelin distribution includes a script directory `scripts/vagrant/zeppelin-dev` -This script creates a virtual machine that launches a repeatable, known set of core dependencies required for developing Zeppelin. It can also be used to run an existing Zeppelin build if you don't plan to build from source. +This script creates a virtual machine that launches a repeatable, known set of core dependencies required for developing Zeppelin. It can also be used to run an existing Zeppelin build if you don't plan to build from source. For PySpark users, this script includes several helpful [Python Libraries](#python-extras). For SparkR users, this script includes several helpful [R Libraries](#r-extras). @@ -88,7 +88,7 @@ By default, Vagrant will share your project directory (the directory with the Va Running the following commands in the guest machine should display these expected versions: `node --version` should report *v0.12.7* -`mvn --version` should report *Apache Maven 3.3.3* and *Java version: 1.7.0_85* +`mvn --version` should report *Apache Maven 3.3.9* and *Java version: 1.7.0_85* The virtual machine consists of: @@ -96,7 +96,7 @@ The virtual machine consists of: - Node.js 0.12.7 - npm 2.11.3 - ruby 1.9.3 + rake, make and bundler (only required if building jekyll documentation) - - Maven 3.3.3 + - Maven 3.3.9 - Git - Unzip - libfontconfig to avoid phatomJs missing dependency issues diff --git a/docs/install/yarn_install.md b/docs/install/yarn_install.md index 466007d2daa..e2427546a66 100644 --- a/docs/install/yarn_install.md +++ b/docs/install/yarn_install.md @@ -1,7 +1,7 @@ --- layout: page title: "Install Zeppelin to connect with existing YARN cluster" -description: "" +description: "This page describes how to pre-configure a bare metal node, configure Apache Zeppelin and connect it to existing YARN cluster running Hortonworks flavour of Hadoop." group: install --- {% include JB/setup %} # Alluxio Interpreter for Apache Zeppelin diff --git a/docs/interpreter/bigquery.md b/docs/interpreter/bigquery.md index 1e92aa98757..7ebe2e2fda8 100644 --- a/docs/interpreter/bigquery.md +++ b/docs/interpreter/bigquery.md @@ -1,9 +1,23 @@ --- layout: page -title: "BigQuery Interpreter" -description: "" +title: "BigQuery Interpreter for Apache Zeppelin" +description: "BigQuery is a highly scalable no-ops data warehouse in the Google Cloud Platform." group: interpreter --- + +{% include JB/setup %} # BigQuery Interpreter for Apache Zeppelin diff --git a/docs/interpreter/cassandra.md b/docs/interpreter/cassandra.md index 89b4ea5806f..5d8929bd588 100644 --- a/docs/interpreter/cassandra.md +++ b/docs/interpreter/cassandra.md @@ -1,9 +1,22 @@ --- layout: page -title: "Cassandra Interpreter" -description: "Cassandra Interpreter" +title: "Cassandra CQL Interpreter for Apache Zeppelin" +description: "Apache Cassandra database is the right choice when you need scalability and high availability without compromising performance." group: interpreter --- + {% include JB/setup %} # Cassandra CQL Interpreter for Apache Zeppelin diff --git a/docs/interpreter/elasticsearch.md b/docs/interpreter/elasticsearch.md index 7c6e2cec4de..0e59cae5051 100644 --- a/docs/interpreter/elasticsearch.md +++ b/docs/interpreter/elasticsearch.md @@ -1,9 +1,22 @@ --- layout: page -title: "Elasticsearch Interpreter" -description: "" +title: "Elasticsearch Interpreter for Apache Zeppelin" +description: "Elasticsearch is a highly scalable open-source full-text search and analytics engine." group: interpreter --- + {% include JB/setup %} # Elasticsearch Interpreter for Apache Zeppelin diff --git a/docs/interpreter/flink.md b/docs/interpreter/flink.md index 95ed6410958..3d065465dbd 100644 --- a/docs/interpreter/flink.md +++ b/docs/interpreter/flink.md @@ -1,9 +1,22 @@ --- layout: page -title: "Flink Interpreter" -description: "" +title: "Flink Interpreter for Apache Zeppelin" +description: "Apache Flink is an open source platform for distributed stream and batch data processing." group: interpreter --- + {% include JB/setup %} # Flink interpreter for Apache Zeppelin diff --git a/docs/interpreter/geode.md b/docs/interpreter/geode.md index 24abf2ea561..f833f9d8911 100644 --- a/docs/interpreter/geode.md +++ b/docs/interpreter/geode.md @@ -1,9 +1,22 @@ --- layout: page -title: "Geode OQL Interpreter" -description: "" +title: "Geode/Gemfire OQL Interpreter for Apache Zeppelin" +description: "Apache Geode (incubating) provides a database-like consistency model, reliable transaction processing and a shared-nothing architecture to maintain very low latency performance with high concurrency processing." group: interpreter --- + {% include JB/setup %} # Geode/Gemfire OQL Interpreter for Apache Zeppelin diff --git a/docs/interpreter/hbase.md b/docs/interpreter/hbase.md index 4598f2fe4be..12e05174359 100644 --- a/docs/interpreter/hbase.md +++ b/docs/interpreter/hbase.md @@ -1,9 +1,22 @@ --- layout: page -title: "HBase Shell Interpreter" -description: "" +title: "HBase Shell Interpreter for Apache Zeppelin" +description: "HBase Shell is a JRuby IRB client for Apache HBase. This interpreter provides all capabilities of Apache HBase shell within Apache Zeppelin." group: interpreter --- + {% include JB/setup %} # HBase Shell Interpreter for Apache Zeppelin diff --git a/docs/interpreter/hdfs.md b/docs/interpreter/hdfs.md index 7369ba0feaa..d7b7bf885d8 100644 --- a/docs/interpreter/hdfs.md +++ b/docs/interpreter/hdfs.md @@ -1,9 +1,22 @@ --- layout: page -title: "HDFS File System Interpreter" -description: "" +title: "HDFS File System Interpreter for Apache Zeppelin" +description: "Hadoop File System is a distributed, fault tolerant file system part of the hadoop project and is often used as storage for distributed processing engines like Hadoop MapReduce and Apache Spark or underlying file systems like Alluxio." group: interpreter --- + {% include JB/setup %} # HDFS File System Interpreter for Apache Zeppelin diff --git a/docs/interpreter/hive.md b/docs/interpreter/hive.md index 037055b2654..feeb7a3f536 100644 --- a/docs/interpreter/hive.md +++ b/docs/interpreter/hive.md @@ -1,9 +1,22 @@ --- layout: page -title: "Hive Interpreter" -description: "" +title: "Hive Interpreter for Apache Zeppelin" +description: "Apache Hive data warehouse software facilitates querying and managing large datasets residing in distributed storage. Hive provides a mechanism to project structure onto this data and query the data using a SQL-like language called HiveQL. At the same time this language also allows traditional map/reduce programmers to plug in their custom mappers and reducers when it is inconvenient or inefficient to express this logic in HiveQL." group: interpreter --- + {% include JB/setup %} # Hive Interpreter for Apache Zeppelin diff --git a/docs/interpreter/ignite.md b/docs/interpreter/ignite.md index d157d3121f2..a085dc1aef2 100644 --- a/docs/interpreter/ignite.md +++ b/docs/interpreter/ignite.md @@ -1,9 +1,22 @@ --- layout: page -title: "Ignite Interpreter" -description: "Ignite user guide" +title: "Ignite Interpreter for Apache Zeppelin" +description: "Apache Ignite in-memory Data Fabric is a high-performance, integrated and distributed in-memory platform for computing and transacting on large-scale data sets in real-time, orders of magnitude faster than possible with traditional disk-based or flash technologies." group: interpreter --- + {% include JB/setup %} # Ignite Interpreter for Apache Zeppelin diff --git a/docs/interpreter/jdbc.md b/docs/interpreter/jdbc.md index f7b60a2b0ba..72963c092b3 100644 --- a/docs/interpreter/jdbc.md +++ b/docs/interpreter/jdbc.md @@ -1,13 +1,25 @@ --- layout: page -title: "Generic JDBC Interpreter" -description: "JDBC user guide" +title: "Generic JDBC Interpreter for Apache Zeppelin" +description: "Generic JDBC Interpreter lets you create a JDBC connection to any data source. You can use Postgres, MySql, MariaDB, Redshift, Apache Hive, Apache Phoenix, Apache Drill and Apache Tajo using JDBC interpreter." group: interpreter --- + {% include JB/setup %} - -# Generic JDBC Interpreter for Apache Zeppelin +# Generic JDBC Interpreter for Apache Zeppelin
    diff --git a/docs/interpreter/lens.md b/docs/interpreter/lens.md index 20b2c0ccb9d..b929220339a 100644 --- a/docs/interpreter/lens.md +++ b/docs/interpreter/lens.md @@ -1,9 +1,22 @@ --- layout: page -title: "Lens Interpreter" -description: "Lens user guide" +title: "Lens Interpreter for Apache Zeppelin" +description: "Apache Lens provides an Unified Analytics interface. Lens aims to cut the Data Analytics silos by providing a single view of data across multiple tiered data stores and optimal execution environment for the analytical query. It seamlessly integrates Hadoop with traditional data warehouses to appear like one." group: interpreter --- + {% include JB/setup %} # Lens Interpreter for Apache Zeppelin diff --git a/docs/interpreter/livy.md b/docs/interpreter/livy.md index 2f47364aae8..1c040b9ff22 100644 --- a/docs/interpreter/livy.md +++ b/docs/interpreter/livy.md @@ -1,9 +1,22 @@ --- layout: page -title: "Livy Interpreter" -description: "" +title: "Livy Interpreter for Apache Zeppelin" +description: "Livy is an open source REST interface for interacting with Spark from anywhere. It supports executing snippets of code or programs in a Spark context that runs locally or in YARN." group: interpreter --- + {% include JB/setup %} # Livy Interpreter for Apache Zeppelin @@ -30,7 +43,7 @@ We added some common configurations for spark, and you can set any configuration This link contains all spark configurations: http://spark.apache.org/docs/latest/configuration.html#available-properties. And instead of starting property with `spark.` it should be replaced with `livy.spark.`. Example: `spark.master` to `livy.spark.master` - + @@ -50,7 +63,7 @@ Example: `spark.master` to `livy.spark.master` - + @@ -102,8 +115,31 @@ Example: `spark.master` to `livy.spark.master` + + + + +
    Property
    zeppelin.livy.spark.maxResult 1000Max number of SparkSQL result to display.Max number of Spark SQL result to display.
    livy.spark.driver.cores Upper bound for the number of executors.
    livy.spark.jars.packagesAdding extra libraries to livy interpreter
    +## Adding External libraries +You can load dynamic library to livy interpreter by set `livy.spark.jars.packages` property to comma-separated list of maven coordinates of jars to include on the driver and executor classpaths. The format for the coordinates should be groupId:artifactId:version. + +Example + + + + + + + + + + + + +
    PropertyExampleDescription
    livy.spark.jars.packagesio.spray:spray-json_2.10:1.3.1Adding extra libraries to livy interpreter
    + ## How to use Basically, you can use diff --git a/docs/interpreter/markdown.md b/docs/interpreter/markdown.md index 42ab86b18c5..84e1395a495 100644 --- a/docs/interpreter/markdown.md +++ b/docs/interpreter/markdown.md @@ -1,9 +1,22 @@ --- layout: page -title: "Markdown Interpreter" -description: "Markdown Interpreter" +title: "Markdown Interpreter for Apache Zeppelin" +description: "Markdown is a plain text formatting syntax designed so that it can be converted to HTML. Apache Zeppelin uses markdown4j." group: interpreter --- + {% include JB/setup %} # Markdown Interpreter for Apache Zeppelin @@ -12,7 +25,7 @@ group: interpreter ## Overview [Markdown](http://daringfireball.net/projects/markdown/) is a plain text formatting syntax designed so that it can be converted to HTML. -Zeppelin uses markdown4j. For more examples and extension support, please checkout [here](https://code.google.com/p/markdown4j/). +Apache Zeppelin uses markdown4j. For more examples and extension support, please checkout [here](https://code.google.com/p/markdown4j/). In Zeppelin notebook, you can use ` %md ` in the beginning of a paragraph to invoke the Markdown interpreter and generate static html from Markdown plain text. In Zeppelin, Markdown interpreter is enabled by default. diff --git a/docs/interpreter/postgresql.md b/docs/interpreter/postgresql.md index a83731bc6cc..20ef43b6c31 100644 --- a/docs/interpreter/postgresql.md +++ b/docs/interpreter/postgresql.md @@ -1,9 +1,22 @@ --- layout: page -title: "PostgreSQL and HAWQ Interpreter" -description: "" +title: "PostgreSQL, Apache HAWQ (incubating) Interpreter for Apache Zeppelin" +description: "Apache Zeppelin supports PostgreSQL, Apache HAWQ(incubating) and Greenplum SQL data processing engines." group: interpreter --- + {% include JB/setup %} # PostgreSQL, Apache HAWQ (incubating) Interpreter for Apache Zeppelin diff --git a/docs/interpreter/python.md b/docs/interpreter/python.md index 107358413a0..4aa3468e640 100644 --- a/docs/interpreter/python.md +++ b/docs/interpreter/python.md @@ -1,9 +1,22 @@ --- layout: page -title: "Python Interpreter" -description: "Python Interpreter" +title: "Python 2 & 3 Interpreter for Apache Zeppelin" +description: "Python is a programming language that lets you work quickly and integrate systems more effectively." group: interpreter --- + {% include JB/setup %} # Python 2 & 3 Interpreter for Apache Zeppelin @@ -74,7 +87,7 @@ print("".join(z.checkbox("f3", [("o1","1"), ("o2","2")],["1"]))) ## Matplotlib integration The python interpreter can display matplotlib graph with the function `z.show()`. - You need to have matplotlib module installed and a XServer running to use this functionality ! + You need to have matplotlib module installed and a XServer running to use this functionality! ```python %python @@ -84,12 +97,12 @@ plt.figure() z.show(plt) plt.close() ``` -z.show function can take optional parameters to adapt graph width and height +The `z.show()` function can take optional parameters to adapt graph dimensions (width and height) as well as output format (png or optionally svg). ```python %python z.show(plt, width='50px') -z.show(plt, height='150px') +z.show(plt, height='150px', fmt='svg') ``` diff --git a/docs/interpreter/r.md b/docs/interpreter/r.md index bc94fff9893..2b224917ceb 100644 --- a/docs/interpreter/r.md +++ b/docs/interpreter/r.md @@ -1,9 +1,22 @@ --- layout: page -title: "R Interpreter" -description: "" +title: "R Interpreter for Apache Zeppelin" +description: "R is a free software environment for statistical computing and graphics." group: interpreter --- + {% include JB/setup %} # R Interpreter for Apache Zeppelin @@ -104,7 +117,7 @@ And vice versa: * The `knitr` environment is persistent. If you run a chunk from Zeppelin that changes a variable, then run the same chunk again, the variable has already been changed. Use immutable variables. -* (Note that `%spark.r` and `$r` are two different ways of calling the same interpreter, as are `%spark.knitr` and `%knitr`. By default, Zeppelin puts the R interpreters in the `%spark.` Interpreter Group. +* (Note that `%spark.r` and `%r` are two different ways of calling the same interpreter, as are `%spark.knitr` and `%knitr`. By default, Zeppelin puts the R interpreters in the `%spark.` Interpreter Group. * Using the `%r` interpreter, if you return a data.frame, HTML, or an image, it will dominate the result. So if you execute three commands, and one is `hist()`, all you will see is the histogram, not the results of the other commands. This is a Zeppelin limitation. diff --git a/docs/interpreter/scalding.md b/docs/interpreter/scalding.md index d2e86e742d9..22027f22dee 100644 --- a/docs/interpreter/scalding.md +++ b/docs/interpreter/scalding.md @@ -1,9 +1,22 @@ --- layout: page -title: "Scalding Interpreter" -description: "" +title: "Scalding Interpreter for Apache Zeppelin" +description: "Scalding is an open source Scala library for writing MapReduce jobs." group: interpreter --- + {% include JB/setup %} # Scalding Interpreter for Apache Zeppelin diff --git a/docs/interpreter/shell.md b/docs/interpreter/shell.md index abcaf1c99d1..50a8ded6ccb 100644 --- a/docs/interpreter/shell.md +++ b/docs/interpreter/shell.md @@ -1,9 +1,22 @@ --- layout: page -title: "Shell Interpreter" -description: "Shell Interpreter" +title: "Shell interpreter for Apache Zeppelin" +description: "Shell interpreter uses Apache Commons Exec to execute external processes." group: interpreter --- + {% include JB/setup %} # Shell interpreter for Apache Zeppelin diff --git a/docs/interpreter/spark.md b/docs/interpreter/spark.md index 81f48443a93..90ac9b0b948 100644 --- a/docs/interpreter/spark.md +++ b/docs/interpreter/spark.md @@ -1,12 +1,24 @@ --- layout: page -title: "Spark Interpreter Group" -description: "" +title: "Apache Spark Interpreter for Apache Zeppelin" +description: "Apache Spark is a fast and general-purpose cluster computing system. It provides high-level APIs in Java, Scala, Python and R, and an optimized engine that supports general execution graphs." group: interpreter --- + {% include JB/setup %} - # Spark Interpreter for Apache Zeppelin
    @@ -79,7 +91,7 @@ You can also set other Spark properties which are not listed in the table. For a spark.executor.memory - 512m + 1g Executor memory per worker instance.
    ex) 512m, 32g @@ -105,7 +117,7 @@ You can also set other Spark properties which are not listed in the table. For a zeppelin.spark.maxResult 1000 - Max number of SparkSQL result to display. + Max number of Spark SQL result to display. zeppelin.spark.printREPLOutput diff --git a/docs/manual/dependencymanagement.md b/docs/manual/dependencymanagement.md index acf6002e8f2..ad8e48371df 100644 --- a/docs/manual/dependencymanagement.md +++ b/docs/manual/dependencymanagement.md @@ -1,7 +1,7 @@ --- layout: page -title: "Dependency Management" -description: "" +title: "Dependency Management for Apache Spark Interpreter" +description: "Include external libraries to Apache Spark Interpreter by setting dependencies in interpreter menu." group: manual --- +{% include JB/setup %} + # Explore Apache Zeppelin UI
    diff --git a/docs/quickstart/tutorial.md b/docs/quickstart/tutorial.md index 9333d1437c7..4947f3ce8a0 100644 --- a/docs/quickstart/tutorial.md +++ b/docs/quickstart/tutorial.md @@ -1,7 +1,7 @@ --- layout: page -title: "Tutorial" -description: "Tutorial is valid for Spark 1.3 and higher" +title: "Apache Zeppelin Tutorial" +description: "This tutorial page contains a short walk-through tutorial that uses Apache Spark backend. Please note that this tutorial is valid for Spark 1.3 and higher." group: quickstart --- +{% include JB/setup %} + # Zeppelin Tutorial
    diff --git a/docs/rest-api/rest-configuration.md b/docs/rest-api/rest-configuration.md index 4323b6d0040..525ddaf9989 100644 --- a/docs/rest-api/rest-configuration.md +++ b/docs/rest-api/rest-configuration.md @@ -1,7 +1,7 @@ --- layout: page -title: "Configuration REST API" -description: "" +title: "Apache Zeppelin Configuration REST API" +description: "This page contains Apache Zeppelin Configuration REST API information." group: rest-api --- +{% include JB/setup %} + + diff --git a/docs/search_data.json b/docs/search_data.json new file mode 100644 index 00000000000..3df19e958d6 --- /dev/null +++ b/docs/search_data.json @@ -0,0 +1,17 @@ +--- +layout: null +--- +{ + {% for page in site.pages %}{% if page.title != nil %} + + "{{ page.url | slugify }}": { + "title": "{{ page.title | xml_escape }}", + "content" : "{{page.content | strip_html | strip_newlines | escape | remove: "\"}}", + "url": " {{ page.url | xml_escape }}", + "group": "{{ page.group }}", + "excerpt": {{ page.description | strip_html | truncatewords: 40 | jsonify }} + } + {% unless forloop.last %},{% endunless %} + {% endif %} + {% endfor %} +} diff --git a/docs/security/authentication.md b/docs/security/authentication.md index 7ce160aa2b5..60352277883 100644 --- a/docs/security/authentication.md +++ b/docs/security/authentication.md @@ -1,7 +1,7 @@ --- layout: page title: "Authentication for NGINX" -description: "Authentication for NGINX" +description: "There are multiple ways to enable authentication in Apache Zeppelin. This page describes HTTP basic auth using NGINX." group: security --- +{% include JB/setup %} + # Authentication for NGINX
    -Authentication is company-specific. +There are multiple ways to enable authentication in Apache Zeppelin. This page describes HTTP basic auth using NGINX. One option is to use [Basic Access Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). ## HTTP Basic Authentication using NGINX diff --git a/docs/security/datasource_authorization.md b/docs/security/datasource_authorization.md index 3f86b8ab00d..edf39fb26cf 100644 --- a/docs/security/datasource_authorization.md +++ b/docs/security/datasource_authorization.md @@ -1,7 +1,7 @@ --- layout: page -title: "Data Source Authorization" -description: "Data Source Authorization" +title: "Data Source Authorization in Apache Zeppelin" +description: "Apache Zeppelin supports protected data sources. In case of a MySql database, every users can set up their own credentials to access it." group: security --- +{% include JB/setup %} + # Data Source Authorization in Apache Zeppelin
    diff --git a/docs/security/notebook_authorization.md b/docs/security/notebook_authorization.md index 87885676ef3..b6511516691 100644 --- a/docs/security/notebook_authorization.md +++ b/docs/security/notebook_authorization.md @@ -1,7 +1,7 @@ --- layout: page -title: "Notebook Authorization" -description: "Notebook Authorization" +title: "Notebook Authorization in Apache Zeppelin" +description: "This page will guide you how you can set the permission for Zeppelin notebooks. This document assumes that Apache Shiro authentication was set up." group: security --- +{% include JB/setup %} + # Zeppelin Notebook Authorization
    diff --git a/docs/security/shiroauthentication.md b/docs/security/shiroauthentication.md index a5c9c317b36..969e61e5543 100644 --- a/docs/security/shiroauthentication.md +++ b/docs/security/shiroauthentication.md @@ -1,7 +1,7 @@ --- layout: page -title: "Shiro Security for Apache Zeppelin" -description: "" +title: "Apache Shiro Authentication for Apache Zeppelin" +description: "Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. This document explains step by step how Shiro can be used for Zeppelin notebook authentication." group: security --- {% include JB/setup %} -# Shiro authentication for Apache Zeppelin +# Apache Shiro authentication for Apache Zeppelin
    @@ -105,6 +105,59 @@ finance = * group1 = * ``` +## Configure Realm (optional) +Realms are responsible for authentication and authorization in Apache Zeppelin. By default, Apache Zeppelin uses [IniRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/text/IniRealm.html) (users and groups are configurable in `conf/shiro.ini` file under `[user]` and `[group]` section). You can also leverage Shiro Realms like [JndiLdapRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/ldap/JndiLdapRealm.html), [JdbcRealm](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/jdbc/JdbcRealm.html) or create [our own](https://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/AuthorizingRealm.html). +To learn more about Apache Shiro Realm, please check [this documentation](http://shiro.apache.org/realm.html). + +We also provide community custom Realms. + +### Active Directory + +``` +activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm +activeDirectoryRealm.systemUsername = userNameA +activeDirectoryRealm.systemPassword = passwordA +activeDirectoryRealm.hadoopSecurityCredentialPath = jceks://file/user/zeppelin/conf/zeppelin.jceks +activeDirectoryRealm.searchBase = CN=Users,DC=SOME_GROUP,DC=COMPANY,DC=COM +activeDirectoryRealm.url = ldap://ldap.test.com:389 +activeDirectoryRealm.groupRolesMap = "CN=aGroupName,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"group1" +activeDirectoryRealm.authorizationCachingEnabled = false +``` + + +Also instead of specifying systemPassword in clear text in shiro.ini administrator can choose to specify the same in "hadoop credential". +Create a keystore file using the hadoop credential commandline, for this the hadoop commons should be in the classpath +`hadoop credential create activeDirectoryRealm.systempassword -provider jceks://file/user/zeppelin/conf/zeppelin.jceks` + +Change the following values in the Shiro.ini file, and uncomment the line: +`activeDirectoryRealm.hadoopSecurityCredentialPath = jceks://file/user/zeppelin/conf/zeppelin.jceks` + +### LDAP + +``` +ldapRealm = org.apache.zeppelin.server.LdapGroupRealm +# search base for ldap groups (only relevant for LdapGroupRealm): +ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM +ldapRealm.contextFactory.url = ldap://ldap.test.com:389 +ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM +ldapRealm.contextFactory.authenticationMechanism = SIMPLE +``` + +### ZeppelinHub +[ZeppelinHub](https://www.zeppelinhub.com) is a service that synchronize your Apache Zeppelin notebooks and enables you to collaborate easily. + +To enable login with your ZeppelinHub credential, apply the following change in `conf/shiro.ini` under `[main]` section. + +``` +### A sample for configuring ZeppelinHub Realm +zeppelinHubRealm = org.apache.zeppelin.realm.ZeppelinHubRealm +## Url of ZeppelinHub +zeppelinHubRealm.zeppelinhubUrl = https://www.zeppelinhub.com +securityManager.realms = $zeppelinHubRealm +``` + +> Note: ZeppelinHub is not releated to apache Zeppelin project. + ## Secure your Zeppelin information (optional) By default, anyone who defined in `[users]` can share **Interpreter Setting**, **Credential** and **Configuration** information in Apache Zeppelin. Sometimes you might want to hide these information for your use case. @@ -123,3 +176,4 @@ If you want to grant this permission to other users, you can change **roles[ ]**
    > **NOTE :** All of the above configurations are defined in the `conf/shiro.ini` file. This documentation is originally from [SECURITY-README.md](https://github.com/apache/zeppelin/blob/master/SECURITY-README.md). + diff --git a/docs/storage/storage.md b/docs/storage/storage.md index 19ddd95af82..76012fff4b4 100644 --- a/docs/storage/storage.md +++ b/docs/storage/storage.md @@ -1,7 +1,7 @@ --- layout: page -title: "Storage" -description: "Notebook Storage option for Zeppelin" +title: "Notebook Storage for Apache Zeppelin" +description: Apache Zeppelin has a pluggable notebook storage mechanism controlled by zeppelin.notebook.storage configuration option with multiple implementations." group: storage --- +{% include JB/setup %} + # Notebook storage options for Apache Zeppelin
    diff --git a/flink/pom.xml b/flink/pom.xml index f4c12d53846..628f5427561 100644 --- a/flink/pom.xml +++ b/flink/pom.xml @@ -120,21 +120,18 @@ org.scala-lang scala-library ${scala.version} - provided org.scala-lang scala-compiler ${scala.version} - provided org.scala-lang scala-reflect ${scala.version} - provided @@ -146,24 +143,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - **/README.md - **/interpreter-setting.json - dependency-reduced-pom.xml - - - @@ -319,7 +298,6 @@ org.apache.maven.plugins maven-dependency-plugin - 2.4 copy-dependencies @@ -339,10 +317,11 @@ + org.apache.maven.plugins maven-dependency-plugin - 2.8 + copy-artifact package copy diff --git a/hbase/src/main/java/org/apache/zeppelin/hbase/HbaseInterpreter.java b/hbase/src/main/java/org/apache/zeppelin/hbase/HbaseInterpreter.java index 289579f4d54..6c2460d6701 100644 --- a/hbase/src/main/java/org/apache/zeppelin/hbase/HbaseInterpreter.java +++ b/hbase/src/main/java/org/apache/zeppelin/hbase/HbaseInterpreter.java @@ -54,23 +54,15 @@ * zeppelin.hbase.test.mode: (Testing only) Disable checks for unit and manual tests. Default: false */ public class HbaseInterpreter extends Interpreter { + public static final String HBASE_HOME = "hbase.home"; + public static final String HBASE_RUBY_SRC = "hbase.ruby.sources"; + public static final String HBASE_TEST_MODE = "zeppelin.hbase.test.mode"; + private Logger logger = LoggerFactory.getLogger(HbaseInterpreter.class); private ScriptingContainer scriptingContainer; private StringWriter writer; - static { - Interpreter.register("hbase", "hbase", HbaseInterpreter.class.getName(), - new InterpreterPropertyBuilder() - .add("hbase.home", - getSystemDefault("HBASE_HOME", "hbase.home", "/usr/lib/hbase/"), - "Installation directory of HBase") - .add("hbase.ruby.sources", "lib/ruby", - "Path to Ruby scripts relative to 'hbase.home'") - .add("zeppelin.hbase.test.mode", "false", "Disable checks for unit and manual tests") - .build()); - } - public HbaseInterpreter(Properties property) { super(property); } @@ -81,9 +73,9 @@ public void open() { this.writer = new StringWriter(); scriptingContainer.setOutput(this.writer); - if (!Boolean.parseBoolean(getProperty("zeppelin.hbase.test.mode"))) { - String hbase_home = getProperty("hbase.home"); - String ruby_src = getProperty("hbase.ruby.sources"); + if (!Boolean.parseBoolean(getProperty(HBASE_TEST_MODE))) { + String hbase_home = getProperty(HBASE_HOME); + String ruby_src = getProperty(HBASE_RUBY_SRC); Path abs_ruby_src = Paths.get(hbase_home, ruby_src).toAbsolutePath(); logger.info("Home:" + hbase_home); @@ -98,7 +90,7 @@ public void open() { logger.info("Absolute Ruby Source:" + abs_ruby_src.toString()); // hirb.rb:41 requires the following system property to be set. Properties sysProps = System.getProperties(); - sysProps.setProperty("hbase.ruby.sources", abs_ruby_src.toString()); + sysProps.setProperty(HBASE_RUBY_SRC, abs_ruby_src.toString()); Path abs_hirb_path = Paths.get(hbase_home, "bin/hirb.rb"); try { diff --git a/hbase/src/main/resources/interpreter-setting.json b/hbase/src/main/resources/interpreter-setting.json new file mode 100644 index 00000000000..be2f01a2478 --- /dev/null +++ b/hbase/src/main/resources/interpreter-setting.json @@ -0,0 +1,25 @@ +[ + { + "group": "hbase", + "name": "hbase", + "className": "org.apache.zeppelin.hbase.HbaseInterpreter", + "properties": { + "hbase.home": { + "envName": "HBASE_HOME", + "propertyName": "hbase.home", + "defaultValue": "/usr/lib/hbase/", + "description": "Installation directory of HBase" + }, + "hbase.ruby.sources": { + "propertyName": "hbase.ruby.sources", + "defaultValue": "lib/ruby", + "description": "Path to Ruby scripts relative to 'hbase.home'" + }, + "zeppelin.hbase.test.mode": { + "propertyName": "zeppelin.hbase.test.mode", + "defaultValue": "false", + "description": "Disable checks for unit and manual tests" + } + } + } +] diff --git a/ignite/pom.xml b/ignite/pom.xml index 66d5aa07f7a..d70c488b88f 100644 --- a/ignite/pom.xml +++ b/ignite/pom.xml @@ -32,7 +32,7 @@ Zeppelin: Apache Ignite interpreter - 1.5.0.final + 1.7.0 @@ -116,8 +116,8 @@ + org.apache.maven.plugins maven-dependency-plugin - 2.8 copy-dependencies diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index d5f623619cd..68a1ce40d1f 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -15,7 +15,8 @@ package org.apache.zeppelin.jdbc; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; - +import java.io.*; +import java.nio.charset.StandardCharsets; import java.io.IOException; import java.security.PrivilegedExceptionAction; import java.sql.Connection; @@ -418,11 +419,11 @@ private InterpreterResult executeSql(String propertyKey, String sql, } catch (Exception e) { logger.error("Cannot run " + sql, e); - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(e.getMessage()).append("\n"); - stringBuilder.append(e.getClass().toString()).append("\n"); - stringBuilder.append(StringUtils.join(e.getStackTrace(), "\n")); - return new InterpreterResult(Code.ERROR, stringBuilder.toString()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + e.printStackTrace(ps); + String errorMsg = new String(baos.toByteArray(), StandardCharsets.UTF_8); + return new InterpreterResult(Code.ERROR, errorMsg); } } @@ -495,7 +496,8 @@ public int getProgress(InterpreterContext context) { public Scheduler getScheduler() { String schedulerName = JDBCInterpreter.class.getName() + this.hashCode(); return isConcurrentExecution() ? - SchedulerFactory.singleton().createOrGetParallelScheduler(schedulerName, 10) + SchedulerFactory.singleton().createOrGetParallelScheduler(schedulerName, + getMaxConcurrentConnection()) : SchedulerFactory.singleton().createOrGetFIFOScheduler(schedulerName); } diff --git a/licenses/LICENSE-lunrjs-0.7.1 b/licenses/LICENSE-lunrjs-0.7.1 new file mode 100644 index 00000000000..e6e4e21b222 --- /dev/null +++ b/licenses/LICENSE-lunrjs-0.7.1 @@ -0,0 +1,19 @@ +Copyright (C) 2013 by Oliver Nightingale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/livy/src/main/resources/interpreter-setting.json b/livy/src/main/resources/interpreter-setting.json index 2c1a0beceba..bc5a27589b2 100644 --- a/livy/src/main/resources/interpreter-setting.json +++ b/livy/src/main/resources/interpreter-setting.json @@ -81,6 +81,11 @@ "propertyName": "zeppelin.livy.keytab", "defaultValue": "", "description": "Kerberos keytab to authenticate livy" + }, + "livy.spark.jars.packages": { + "propertyName": "livy.spark.jars.packages", + "defaultValue": "", + "description": "Adding extra libraries to livy interpreter" } } }, @@ -93,7 +98,7 @@ "envName": "ZEPPELIN_LIVY_MAXRESULT", "propertyName": "zeppelin.livy.spark.sql.maxResult", "defaultValue": "1000", - "description": "Max number of SparkSQL result to display." + "description": "Max number of Spark SQL result to display." }, "zeppelin.livy.concurrentSQL": { "propertyName": "zeppelin.livy.concurrentSQL", @@ -116,4 +121,4 @@ "properties": { } } -] \ No newline at end of file +] diff --git a/notebook/r/note.json b/notebook/2BWJFTXKJ/note.json similarity index 98% rename from notebook/r/note.json rename to notebook/2BWJFTXKJ/note.json index 35c2bbbbee9..e97dfefdde9 100644 --- a/notebook/r/note.json +++ b/notebook/2BWJFTXKJ/note.json @@ -3,7 +3,7 @@ { "title": "Hello R", "text": "%r\nfoo \u003c- TRUE\nprint(foo)\nbare \u003c- c(1, 2.5, 4)\nprint(bare)\ndouble \u003c- 15.0\nprint(double)", - "dateUpdated": "Feb 23, 2016 2:44:13 PM", + "dateUpdated": "Feb 23, 2016 2:44:13 AM", "config": { "colWidth": 4.0, "graph": { @@ -23,6 +23,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1429882946244_-381648689", "id": "20150424-154226_261270952", "result": { @@ -30,16 +31,16 @@ "type": "TEXT", "msg": "[1] TRUE\n[1] 1.0 2.5 4.0\n[1] 15" }, - "dateCreated": "Apr 24, 2015 3:42:26 PM", - "dateStarted": "Feb 23, 2016 2:44:13 PM", - "dateFinished": "Feb 23, 2016 2:44:55 PM", + "dateCreated": "Apr 24, 2015 3:42:26 AM", + "dateStarted": "Feb 23, 2016 2:44:13 AM", + "dateFinished": "Feb 23, 2016 2:44:55 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Load R Librairies", "text": "%r\nlibrary(data.table)\ndt \u003c- data.table(1:3)\nprint(dt)\nfor (i in 1:5) {\n print(i*2)\n}\nprint(1:50)", - "dateUpdated": "Feb 23, 2016 2:45:12 PM", + "dateUpdated": "Feb 23, 2016 2:45:12 AM", "config": { "colWidth": 4.0, "graph": { @@ -59,6 +60,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1429882976611_1352445253", "id": "20150424-154256_645296307", "result": { @@ -66,16 +68,16 @@ "type": "TEXT", "msg": "V1\n1: 1\n2: 2\n3: 3\n[1] 2\n[1] 4\n[1] 6\n[1] 8\n[1] 10\n [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n[24] 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46\n[47] 47 48 49 50" }, - "dateCreated": "Apr 24, 2015 3:42:56 PM", - "dateStarted": "Feb 23, 2016 2:45:13 PM", - "dateFinished": "Feb 23, 2016 2:45:13 PM", + "dateCreated": "Apr 24, 2015 3:42:56 AM", + "dateStarted": "Feb 23, 2016 2:45:13 AM", + "dateFinished": "Feb 23, 2016 2:45:13 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Load Iris Dataset", "text": "%r\ncolnames(iris)\niris$Petal.Length\niris$Sepal.Length", - "dateUpdated": "Feb 23, 2016 2:45:14 PM", + "dateUpdated": "Feb 23, 2016 2:45:14 AM", "config": { "colWidth": 4.0, "graph": { @@ -95,6 +97,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455138077044_161383897", "id": "20160210-220117_115873183", "result": { @@ -102,16 +105,16 @@ "type": "TEXT", "msg": "[1] “Sepal.Length” “Sepal.Width” “Petal.Length” “Petal.Width” \n[5] “Species”\u003cbr /\u003e\n [1] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 1.5 1.6 1.4 1.1 1.2 1.5 1.3\n [18] 1.4 1.7 1.5 1.7 1.5 1.0 1.7 1.9 1.6 1.6 1.5 1.4 1.6 1.6 1.5 1.5 1.4\n [35] 1.5 1.2 1.3 1.4 1.3 1.5 1.3 1.3 1.3 1.6 1.9 1.4 1.6 1.4 1.5 1.4 4.7\n [52] 4.5 4.9 4.0 4.6 4.5 4.7 3.3 4.6 3.9 3.5 4.2 4.0 4.7 3.6 4.4 4.5 4.1\n [69] 4.5 3.9 4.8 4.0 4.9 4.7 4.3 4.4 4.8 5.0 4.5 3.5 3.8 3.7 3.9 5.1 4.5\n [86] 4.5 4.7 4.4 4.1 4.0 4.4 4.6 4.0 3.3 4.2 4.2 4.2 4.3 3.0 4.1 6.0 5.1\n[103] 5.9 5.6 5.8 6.6 4.5 6.3 5.8 6.1 5.1 5.3 5.5 5.0 5.1 5.3 5.5 6.7 6.9\n[120] 5.0 5.7 4.9 6.7 4.9 5.7 6.0 4.8 4.9 5.6 5.8 6.1 6.4 5.6 5.1 5.6 6.1\n[137] 5.6 5.5 4.8 5.4 5.6 5.1 5.1 5.9 5.7 5.2 5.0 5.2 5.4 5.1\n [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8 4.3 5.8 5.7 5.4\n [18] 5.1 5.7 5.1 5.4 5.1 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5\n [35] 4.9 5.0 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 5.3 5.0 7.0\n [52] 6.4 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7 5.6 5.8\n [69] 6.2 5.6 5.9 6.1 6.3 6.1 6.4 6.6 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4\n [86] 6.0 6.7 6.3 5.6 5.5 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1 5.7 6.3 5.8\n[103] 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2 6.5 6.4 6.8 5.7 5.8 6.4 6.5 7.7 7.7\n[120] 6.0 6.9 5.6 7.7 6.3 6.7 7.2 6.2 6.1 6.4 7.2 7.4 7.9 6.4 6.3 6.1 7.7\n[137] 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8 6.7 6.7 6.3 6.5 6.2 5.9" }, - "dateCreated": "Feb 10, 2016 10:01:17 PM", - "dateStarted": "Feb 23, 2016 2:45:14 PM", - "dateFinished": "Feb 23, 2016 2:45:14 PM", + "dateCreated": "Feb 10, 2016 10:01:17 AM", + "dateStarted": "Feb 23, 2016 2:45:14 AM", + "dateFinished": "Feb 23, 2016 2:45:14 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "HTML Display", "text": "%r \n\nprint(\"%html \u003ch3\u003eHello HTML\u003c/h3\u003e\")\nprint(\"\u003cfont color\u003d\u0027blue\u0027\u003e\u003cspan class\u003d\u0027fa fa-bars\u0027\u003e Easy...\u003c/font\u003e\u003c/span\u003e\")\nfor (i in 1:10) {\n print(paste0(\"\u003ch4\u003e\", i, \" * 2 \u003d \", i*2, \"\u003c/h4\u003e\"))\n}", - "dateUpdated": "Feb 23, 2016 2:45:15 PM", + "dateUpdated": "Feb 23, 2016 2:45:15 AM", "config": { "colWidth": 3.0, "graph": { @@ -131,6 +134,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1456140102445_51059930", "id": "20160222-122142_1323614681", "result": { @@ -139,15 +143,15 @@ "msg": "\u003cp\u003e\u003c/p\u003e\u003ch3\u003eHello HTML\u003c/h3\u003e\u003cfont color\u003d\"blue\"\u003e\u003cspan class\u003d\"fa fa-bars\"\u003e Easy…\u003c/span\u003e\u003c/font\u003e\u003ch4\u003e1 * 2 \u003d 2\u003c/h4\u003e\u003ch4\u003e2 * 2 \u003d 4\u003c/h4\u003e\u003ch4\u003e3 * 2 \u003d 6\u003c/h4\u003e\u003ch4\u003e4 * 2 \u003d 8\u003c/h4\u003e\u003ch4\u003e5 * 2 \u003d 10\u003c/h4\u003e\u003ch4\u003e6 * 2 \u003d 12\u003c/h4\u003e\u003ch4\u003e7 * 2 \u003d 14\u003c/h4\u003e\u003ch4\u003e8 * 2 \u003d 16\u003c/h4\u003e\u003ch4\u003e9 * 2 \u003d 18\u003c/h4\u003e\u003ch4\u003e10 * 2 \u003d 20\u003c/h4\u003e\u003cp\u003e\u003c/p\u003e" }, "dateCreated": "Feb 22, 2016 12:21:42 PM", - "dateStarted": "Feb 23, 2016 2:45:15 PM", - "dateFinished": "Feb 23, 2016 2:45:16 PM", + "dateStarted": "Feb 23, 2016 2:45:15 AM", + "dateFinished": "Feb 23, 2016 2:45:16 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "TABLE Display", "text": "%r print(\"%table name\\tsize\\nsmall\\t100\\nlarge\\t1000\")", - "dateUpdated": "Feb 23, 2016 2:45:18 PM", + "dateUpdated": "Feb 23, 2016 2:45:18 AM", "config": { "colWidth": 3.0, "graph": { @@ -190,6 +194,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1456216582752_6855525", "id": "20160223-093622_330111284", "result": { @@ -198,15 +203,15 @@ "msg": "name\tsize\nsmall\t100\nlarge\t1000" }, "dateCreated": "Feb 23, 2016 9:36:22 AM", - "dateStarted": "Feb 23, 2016 2:45:18 PM", - "dateFinished": "Feb 23, 2016 2:45:18 PM", + "dateStarted": "Feb 23, 2016 2:45:18 AM", + "dateFinished": "Feb 23, 2016 2:45:18 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "IMG Display", "text": "%r print(paste0(\"%img \", \"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\u003d\"))", - "dateUpdated": "Feb 23, 2016 2:47:07 PM", + "dateUpdated": "Feb 23, 2016 2:47:07 AM", "config": { "colWidth": 3.0, "graph": { @@ -227,6 +232,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1456216588116_1304228816", "id": "20160223-093628_594223067", "result": { @@ -235,14 +241,14 @@ "msg": "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\u003d" }, "dateCreated": "Feb 23, 2016 9:36:28 AM", - "dateStarted": "Feb 23, 2016 2:47:07 PM", - "dateFinished": "Feb 23, 2016 2:47:07 PM", + "dateStarted": "Feb 23, 2016 2:47:07 AM", + "dateFinished": "Feb 23, 2016 2:47:07 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "z.input(\"name\", \"sun\")\n\n\n\n\n\n", - "dateUpdated": "Feb 23, 2016 3:06:16 PM", + "dateUpdated": "Feb 23, 2016 3:06:16 AM", "config": { "colWidth": 3.0, "graph": { @@ -270,6 +276,7 @@ } } }, + "apps": [], "jobName": "paragraph_1456235221022_-1625740722", "id": "20160223-144701_1698149301", "result": { @@ -277,16 +284,16 @@ "type": "TEXT", "msg": "res13: Object \u003d sun\n" }, - "dateCreated": "Feb 23, 2016 2:47:01 PM", - "dateStarted": "Feb 23, 2016 3:06:16 PM", - "dateFinished": "Feb 23, 2016 3:06:16 PM", + "dateCreated": "Feb 23, 2016 2:47:01 AM", + "dateStarted": "Feb 23, 2016 3:06:16 AM", + "dateFinished": "Feb 23, 2016 3:06:16 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "[WIP] Dynamic Form", "text": "%r \nprint(paste0(\"%html Hello \", z.input(\"name\", \"sun\")))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", - "dateUpdated": "Feb 23, 2016 3:06:30 PM", + "dateUpdated": "Feb 23, 2016 3:06:30 AM", "config": { "colWidth": 3.0, "graph": { @@ -306,6 +313,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1456217039220_218125354", "id": "20160223-094359_718035548", "result": { @@ -314,15 +322,14 @@ "msg": "\u003cp\u003eHello sun\u003c/p\u003e" }, "dateCreated": "Feb 23, 2016 9:43:59 AM", - "dateStarted": "Feb 23, 2016 3:01:38 PM", - "dateFinished": "Feb 23, 2016 3:01:38 PM", + "dateStarted": "Feb 23, 2016 3:01:38 AM", + "dateFinished": "Feb 23, 2016 3:01:38 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Write Scala To R", "text": "val s \u003d \"Hello R from Scala\"\nz.put(\"s\", s)\nval b \u003d new Integer(42)\nz.put(\"b\", b)\nval a: Array[Double] \u003d Array[Double](30.1, 20.0)\nz.put(\"a\", a)\nval m \u003d Array(Array(1, 4), Array(8, 16))\nz.put(\"m\", m)\nval v \u003d Vector(1, 2, 3, 4)\nz.put(\"v\", v)", - "authenticationInfo": {}, "dateUpdated": "Mar 30, 2016 9:05:07 AM", "config": { "colWidth": 3.0, @@ -343,6 +350,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1429862281402_-79250404", "id": "20150424-095801_125725189", "result": { @@ -359,7 +367,6 @@ { "title": "Read from R the Scala Variables", "text": "%r\nz.get(\"s\")\nz.get(\"b\")\nprint(unlist(z.get(\"a\")))\nprint(unlist(z.get(\"m\")))\nz.get(\"v\")", - "authenticationInfo": {}, "dateUpdated": "Mar 30, 2016 9:05:08 AM", "config": { "colWidth": 3.0, @@ -380,6 +387,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1438930802740_-1781296534", "id": "20150807-090002_1514685133", "result": { @@ -396,7 +404,6 @@ { "title": "Write R to Scala", "text": "%r\ns \u003c- \"Hello Scala from R\"\nprint(s)\nz.put(\"rs\", s)\nb \u003c- TRUE\nprint(b)\nz.put(\"rb\", b)\nd \u003c- 15.0\nprint(d)\nz.put(\"rd\", d)\nm \u003c- c(2.4, 2.5, 4)\nprint(m)\nz.put(\"rm\", m)", - "authenticationInfo": {}, "dateUpdated": "Mar 30, 2016 9:05:25 AM", "config": { "colWidth": 3.0, @@ -417,6 +424,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455137934157_-1786381957", "id": "20160210-215854_620520530", "result": { @@ -424,7 +432,7 @@ "type": "TEXT", "msg": "[1] “Hello Scala from R”\nNULL\n[1] TRUE\nNULL\n[1] 15\nNULL\n[1] 2.4 2.5 4.0\nNULL" }, - "dateCreated": "Feb 10, 2016 9:58:54 PM", + "dateCreated": "Feb 10, 2016 9:58:54 AM", "dateStarted": "Mar 30, 2016 9:05:25 AM", "dateFinished": "Mar 30, 2016 9:05:25 AM", "status": "FINISHED", @@ -433,7 +441,6 @@ { "title": "Read from Scala the R Variables", "text": "println(\"rs \u003d \"+ z.get(\"rs\"))\nprintln(\"rb \u003d \"+ z.get(\"rb\"))\nprintln(\"rd \u003d \"+ z.get(\"rd\"))\nprintln(\"rm \u003d \"+ z.get(\"rm\"))\n// println(z.get(\"rm\").getClass)\n// println(\"rm \u003d \"+ z.get(\"rm\").asInstanceOf[Array[Double]].toSeq)", - "authenticationInfo": {}, "dateUpdated": "Mar 30, 2016 9:05:27 AM", "config": { "colWidth": 3.0, @@ -454,6 +461,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455138066039_1048230112", "id": "20160210-220106_141884849", "result": { @@ -461,7 +469,7 @@ "type": "TEXT", "msg": "rs \u003d Hello Scala from R\nrb \u003d true\nrd \u003d 15.0\nrm \u003d 2.4\n" }, - "dateCreated": "Feb 10, 2016 10:01:06 PM", + "dateCreated": "Feb 10, 2016 10:01:06 AM", "dateStarted": "Mar 30, 2016 9:05:27 AM", "dateFinished": "Mar 30, 2016 9:05:27 AM", "status": "FINISHED", @@ -470,7 +478,7 @@ { "title": "Create a Spark Dataframe", "text": "import org.apache.commons.io.IOUtils\nimport java.net.URL\nimport java.nio.charset.Charset\n\nval bankText \u003d sc.parallelize(\n IOUtils.toString(\n new URL(\"https://s3.amazonaws.com/apache-zeppelin/tutorial/bank/bank.csv\"),\n Charset.forName(\"utf8\")).split(\"\\n\"))\n\ncase class Bank(age: Integer, job: String, marital: String, education: String, balance: Integer)\n\nval bank \u003d bankText.map(s \u003d\u003e s.split(\";\")).filter(s \u003d\u003e s(0) !\u003d \"\\\"age\\\"\").map(\n s \u003d\u003e Bank(s(0).toInt, \n s(1).replaceAll(\"\\\"\", \"\"),\n s(2).replaceAll(\"\\\"\", \"\"),\n s(3).replaceAll(\"\\\"\", \"\"),\n s(5).replaceAll(\"\\\"\", \"\").toInt\n )\n).toDF()\nbank.registerTempTable(\"bank\")", - "dateUpdated": "Feb 23, 2016 2:49:36 PM", + "dateUpdated": "Feb 23, 2016 2:49:36 AM", "config": { "colWidth": 6.0, "graph": { @@ -490,6 +498,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455142039343_-233762796", "id": "20160210-230719_2111095838", "result": { @@ -497,16 +506,16 @@ "type": "TEXT", "msg": "import org.apache.commons.io.IOUtils\nimport java.net.URL\nimport java.nio.charset.Charset\nbankText: org.apache.spark.rdd.RDD[String] \u003d ParallelCollectionRDD[0] at parallelize at \u003cconsole\u003e:27\ndefined class Bank\nbank: org.apache.spark.sql.DataFrame \u003d [age: int, job: string, marital: string, education: string, balance: int]\n" }, - "dateCreated": "Feb 10, 2016 11:07:19 PM", - "dateStarted": "Feb 23, 2016 2:49:36 PM", - "dateFinished": "Feb 23, 2016 2:49:41 PM", + "dateCreated": "Feb 10, 2016 11:07:19 AM", + "dateStarted": "Feb 23, 2016 2:49:36 AM", + "dateFinished": "Feb 23, 2016 2:49:41 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Read the Spark Dataframe from R", "text": "%r\n\ndf \u003c- sql(sqlContext, \"select count(*) from bank\")\nprintSchema(df)\nSparkR::head(df)", - "dateUpdated": "Feb 23, 2016 2:49:52 PM", + "dateUpdated": "Feb 23, 2016 2:49:52 AM", "config": { "colWidth": 6.0, "graph": { @@ -527,6 +536,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455142043062_1598026718", "id": "20160210-230723_1811469598", "result": { @@ -534,16 +544,16 @@ "type": "TEXT", "msg": "root\n |– _c0: long (nullable \u003d false)\n _c0\n1 4521" }, - "dateCreated": "Feb 10, 2016 11:07:23 PM", - "dateStarted": "Feb 23, 2016 2:49:52 PM", - "dateFinished": "Feb 23, 2016 2:49:54 PM", + "dateCreated": "Feb 10, 2016 11:07:23 AM", + "dateStarted": "Feb 23, 2016 2:49:52 AM", + "dateFinished": "Feb 23, 2016 2:49:54 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Query with Spark Dataframe with SQL", "text": "%sql select count(*) from bank", - "dateUpdated": "Feb 23, 2016 2:49:56 PM", + "dateUpdated": "Feb 23, 2016 2:49:56 AM", "config": { "colWidth": 6.0, "graph": { @@ -563,6 +573,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455142050697_-1353382095", "id": "20160210-230730_1259663883", "result": { @@ -570,16 +581,16 @@ "type": "TABLE", "msg": "_c0\n4521\n" }, - "dateCreated": "Feb 10, 2016 11:07:30 PM", - "dateStarted": "Feb 23, 2016 2:49:56 PM", - "dateFinished": "Feb 23, 2016 2:49:56 PM", + "dateCreated": "Feb 10, 2016 11:07:30 AM", + "dateStarted": "Feb 23, 2016 2:49:56 AM", + "dateFinished": "Feb 23, 2016 2:49:56 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Create a R Dataframe", "text": "%r \n\nlocalNames \u003c- data.frame(name\u003dc(\"John\", \"Smith\", \"Sarah\"), budget\u003dc(19, 53, 18))\nnames \u003c- createDataFrame(sqlContext, localNames)\nprintSchema(names)\nregisterTempTable(names, \"names\")\n\n# SparkR::head(names)", - "dateUpdated": "Feb 23, 2016 2:50:02 PM", + "dateUpdated": "Feb 23, 2016 2:50:02 AM", "config": { "colWidth": 6.0, "graph": { @@ -599,6 +610,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455142112413_519883679", "id": "20160210-230832_1847721959", "result": { @@ -606,16 +618,16 @@ "type": "TEXT", "msg": "root\n |– name: string (nullable \u003d true)\n |– budget: double (nullable \u003d true)" }, - "dateCreated": "Feb 10, 2016 11:08:32 PM", - "dateStarted": "Feb 23, 2016 2:50:02 PM", - "dateFinished": "Feb 23, 2016 2:50:02 PM", + "dateCreated": "Feb 10, 2016 11:08:32 AM", + "dateStarted": "Feb 23, 2016 2:50:02 AM", + "dateFinished": "Feb 23, 2016 2:50:02 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Read the R Dataframe from Spark", "text": "sqlc.sql(\"select * from names\").head", - "dateUpdated": "Feb 23, 2016 2:50:09 PM", + "dateUpdated": "Feb 23, 2016 2:50:09 AM", "config": { "colWidth": 6.0, "graph": { @@ -635,6 +647,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455188357108_95477841", "id": "20160211-115917_445850505", "result": { @@ -643,15 +656,15 @@ "msg": "res32: org.apache.spark.sql.Row \u003d [John,19.0]\n" }, "dateCreated": "Feb 11, 2016 11:59:17 AM", - "dateStarted": "Feb 23, 2016 2:50:09 PM", - "dateFinished": "Feb 23, 2016 2:50:10 PM", + "dateStarted": "Feb 23, 2016 2:50:09 AM", + "dateFinished": "Feb 23, 2016 2:50:10 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "Query the R Datafame with SQL", "text": "%sql select * from names\n", - "dateUpdated": "Feb 23, 2016 2:50:15 PM", + "dateUpdated": "Feb 23, 2016 2:50:15 AM", "config": { "colWidth": 6.0, "graph": { @@ -683,6 +696,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455142115582_-1840950897", "id": "20160210-230835_19876971", "result": { @@ -690,15 +704,15 @@ "type": "TABLE", "msg": "name\tbudget\nJohn\t19.0\nSmith\t53.0\nSarah\t18.0\n" }, - "dateCreated": "Feb 10, 2016 11:08:35 PM", - "dateStarted": "Feb 23, 2016 2:50:15 PM", - "dateFinished": "Feb 23, 2016 2:50:15 PM", + "dateCreated": "Feb 10, 2016 11:08:35 AM", + "dateStarted": "Feb 23, 2016 2:50:15 AM", + "dateFinished": "Feb 23, 2016 2:50:15 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%r pairs(iris)", - "dateUpdated": "Feb 23, 2016 2:50:20 PM", + "dateUpdated": "Feb 23, 2016 2:50:20 AM", "config": { "colWidth": 4.0, "graph": { @@ -717,6 +731,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455137735427_-1023869289", "id": "20160210-215535_1815168219", "result": { @@ -724,15 +739,15 @@ "type": "HTML", "msg": "\u003cp\u003e\u003cimg src\u003d\"data:image/png;base64,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\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"100%\" /\u003e \u003c/p\u003e" }, - "dateCreated": "Feb 10, 2016 9:55:35 PM", - "dateStarted": "Feb 23, 2016 2:50:20 PM", - "dateFinished": "Feb 23, 2016 2:50:20 PM", + "dateCreated": "Feb 10, 2016 9:55:35 AM", + "dateStarted": "Feb 23, 2016 2:50:20 AM", + "dateFinished": "Feb 23, 2016 2:50:20 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%r {\"imageWidth\": \"400px\"} plot(iris, col \u003d heat.colors(3))", - "dateUpdated": "Feb 23, 2016 2:50:22 PM", + "dateUpdated": "Feb 23, 2016 2:50:22 AM", "config": { "colWidth": 4.0, "graph": { @@ -751,6 +766,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455137737773_-549089146", "id": "20160210-215537_582262164", "result": { @@ -758,15 +774,15 @@ "type": "HTML", "msg": "\u003cp\u003e\u003cimg src\u003d\"data:image/png;base64,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\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"400px\" /\u003e \u003c/p\u003e" }, - "dateCreated": "Feb 10, 2016 9:55:37 PM", - "dateStarted": "Feb 23, 2016 2:50:22 PM", - "dateFinished": "Feb 23, 2016 2:50:22 PM", + "dateCreated": "Feb 10, 2016 9:55:37 AM", + "dateStarted": "Feb 23, 2016 2:50:22 AM", + "dateFinished": "Feb 23, 2016 2:50:22 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%r {\"imageWidth\": \"250px\"}\n\nmosaicplot(Titanic, main \u003d \"Survival on the Titanic\")\n\npairs(mtcars)\nlibrary(data.table)\n\ndt \u003c- data.table(1:30)\nplot(dt[[1]], 1:30)", - "dateUpdated": "Feb 23, 2016 2:50:23 PM", + "dateUpdated": "Feb 23, 2016 2:50:23 AM", "config": { "colWidth": 4.0, "graph": { @@ -785,6 +801,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455130080006_-1413003071", "id": "20160210-194800_298249044", "result": { @@ -792,16 +809,16 @@ "type": "HTML", "msg": "\u003cp\u003e\u003cimg src\u003d\"data:image/png;base64,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\u003d\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"250px\" /\u003e \u003cimg src\u003d\"data:image/png;base64,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\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"250px\" /\u003e \u003cimg src\u003d\"data:image/png;base64,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\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"250px\" /\u003e \u003c/p\u003e" }, - "dateCreated": "Feb 10, 2016 7:48:00 PM", - "dateStarted": "Feb 23, 2016 2:50:23 PM", - "dateFinished": "Feb 23, 2016 2:50:24 PM", + "dateCreated": "Feb 10, 2016 7:48:00 AM", + "dateStarted": "Feb 23, 2016 2:50:23 AM", + "dateFinished": "Feb 23, 2016 2:50:24 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "RCharts Map", "text": "%r\nrequire(rCharts)\nmap3 \u003c- Leaflet$new()\nmap3$setView(c(51.505, -0.09), zoom \u003d 13)\nmap3$marker(c(51.5, -0.09), bindPopup \u003d \"\u003cp\u003e Hi. I am a popup \u003c/p\u003e\")\nmap3$marker(c(51.495, -0.083), bindPopup \u003d \"\u003cp\u003e Hi. I am another popup \u003c/p\u003e\")\nmap3$print(\"map3\", include_assets\u003dTRUE, cdn\u003dTRUE)\n", - "dateUpdated": "Feb 23, 2016 2:50:25 PM", + "dateUpdated": "Feb 23, 2016 2:50:25 AM", "config": { "colWidth": 6.0, "graph": { @@ -823,6 +840,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455141266539_-447013854", "id": "20160210-225426_326747114", "result": { @@ -830,16 +848,16 @@ "type": "HTML", "msg": "\u003cp\u003e\u003clink rel\u003d\"stylesheet\" href\u003d\"http://cdn.leafletjs.com/leaflet-0.5.1/leaflet.css\" /\u003e\u003c/p\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://cdn.leafletjs.com/leaflet-0.5.1/leaflet.js\"\u003e\u003c/script\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://rawgithub.com/leaflet-extras/leaflet-providers/gh-pages/leaflet-providers.js\"\u003e\u003c/script\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://harrywood.co.uk/maps/examples/leaflet/leaflet-plugins/layer/vector/KML.js\"\u003e\u003c/script\u003e\u003cp\u003e\u003cstyle\u003e\n .rChart {\n display: block;\n margin-left: auto; \n margin-right: auto;\n width: 800px;\n height: 400px;\n }\u003cbr/\u003e\n \u003c/style\u003e\u003c/p\u003e\u003cdiv id\u003d\"map3\" class\u003d\"rChart leaflet\"\u003e\u003c/div\u003e\u003cscript\u003e\n var spec \u003d {\n \"dom\": \"map3\",\n\"width\": 800,\n\"height\": 400,\n\"urlTemplate\": \"http://{s}.tile.osm.org/{z}/{x}/{y}.png\",\n\"layerOpts\": {\n \"attribution\": \"Map data\u003ca href\u003d\\\"http://openstreetmap.org\\\"\u003eOpenStreetMap\u003c/a\u003e\\n contributors, Imagery\u003ca href\u003d\\\"http://mapbox.com\\\"\u003eMapBox\u003c/a\u003e\" \n},\n\"center\": [ 51.505, -0.09 ],\n\"zoom\": 13,\n\"id\": \"map3\" \n}\n\n var map \u003d L.map(spec.dom, spec.mapOpts)\n \n map.setView(spec.center, spec.zoom);\n\n if (spec.provider){\n L.tileLayer.provider(spec.provider).addTo(map) \n } else {\n L.tileLayer(spec.urlTemplate, spec.layerOpts).addTo(map)\n }\n \n L\n .marker([\n 51.5,\n -0.09 \n])\n .addTo( map )\n .bindPopup(\"\u003cp\u003e Hi. I am a popup \u003c/p\u003e\")\nL\n .marker([\n 51.495,\n-0.083 \n])\n .addTo( map )\n .bindPopup(\"\u003cp\u003e Hi. I am another popup \u003c/p\u003e\")\n \n \n \n \n if (spec.circle2){\n for (var c in spec.circle2){\n var circle \u003d L.circle(c.center, c.radius, c.opts)\n .addTo(map);\n }\n }\n \n \n \n \n \n \n \n \n\u003c/script\u003e" }, - "dateCreated": "Feb 10, 2016 10:54:26 PM", - "dateStarted": "Feb 23, 2016 2:50:25 PM", - "dateFinished": "Feb 23, 2016 2:50:26 PM", + "dateCreated": "Feb 10, 2016 10:54:26 AM", + "dateStarted": "Feb 23, 2016 2:50:25 AM", + "dateFinished": "Feb 23, 2016 2:50:26 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "title": "GoogleViz", "text": "%r\nlibrary(googleVis)\nbubble \u003c- gvisBubbleChart(Fruits, idvar\u003d\"Fruit\", \n xvar\u003d\"Sales\", yvar\u003d\"Expenses\",\n colorvar\u003d\"Year\", sizevar\u003d\"Profit\",\n options\u003dlist(\n hAxis\u003d\u0027{minValue:75, maxValue:125}\u0027))\nprint(bubble, tag \u003d \u0027chart\u0027)", - "dateUpdated": "Feb 23, 2016 2:50:27 PM", + "dateUpdated": "Feb 23, 2016 2:50:27 AM", "config": { "colWidth": 6.0, "graph": { @@ -859,6 +877,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455141578555_-1713165000", "id": "20160210-225938_1538591791", "result": { @@ -866,15 +885,15 @@ "type": "HTML", "msg": "\u003c!-- BubbleChart generated in R 3.1.2 by googleVis 0.5.10 package --\u003e\n\n\u003c!-- Tue Feb 23 14:50:27 2016 --\u003e\n\n\u003c!-- jsHeader --\u003e\n\n\u003cscript type\u003d\"text/javascript\"\u003e\n \n// jsData \nfunction gvisDataBubbleChartID3c7e5eba3096 () {\nvar data \u003d new google.visualization.DataTable();\nvar datajson \u003d\n[\n [\n \"Apples\",\n98,\n78,\n\"2008\",\n20 \n],\n[\n \"Apples\",\n111,\n79,\n\"2009\",\n32 \n],\n[\n \"Apples\",\n89,\n76,\n\"2010\",\n13 \n],\n[\n \"Oranges\",\n96,\n81,\n\"2008\",\n15 \n],\n[\n \"Bananas\",\n85,\n76,\n\"2008\",\n9 \n],\n[\n \"Oranges\",\n93,\n80,\n\"2009\",\n13 \n],\n[\n \"Bananas\",\n94,\n78,\n\"2009\",\n16 \n],\n[\n \"Oranges\",\n98,\n91,\n\"2010\",\n7 \n],\n[\n \"Bananas\",\n81,\n71,\n\"2010\",\n10 \n] \n];\ndata.addColumn(\u0027string\u0027,\u0027Fruit\u0027);\ndata.addColumn(\u0027number\u0027,\u0027Sales\u0027);\ndata.addColumn(\u0027number\u0027,\u0027Expenses\u0027);\ndata.addColumn(\u0027string\u0027,\u0027Year\u0027);\ndata.addColumn(\u0027number\u0027,\u0027Profit\u0027);\ndata.addRows(datajson);\nreturn(data);\n}\n \n// jsDrawChart\nfunction drawChartBubbleChartID3c7e5eba3096() {\nvar data \u003d gvisDataBubbleChartID3c7e5eba3096();\nvar options \u003d {};\noptions[\"hAxis\"] \u003d {minValue:75, maxValue:125};\n\n var chart \u003d new google.visualization.BubbleChart(\n document.getElementById(\u0027BubbleChartID3c7e5eba3096\u0027)\n );\n chart.draw(data,options);\n \n\n}\n \n \n// jsDisplayChart\n(function() {\nvar pkgs \u003d window.__gvisPackages \u003d window.__gvisPackages || [];\nvar callbacks \u003d window.__gvisCallbacks \u003d window.__gvisCallbacks || [];\nvar chartid \u003d \"corechart\";\n \n// Manually see if chartid is in pkgs (not all browsers support Array.indexOf)\nvar i, newPackage \u003d true;\nfor (i \u003d 0; newPackage \u0026\u0026 i \u003c pkgs.length; i++) {\nif (pkgs[i] \u003d\u003d\u003d chartid)\nnewPackage \u003d false;\n}\nif (newPackage)\n pkgs.push(chartid);\n \n// Add the drawChart function to the global list of callbacks\ncallbacks.push(drawChartBubbleChartID3c7e5eba3096);\n})();\nfunction displayChartBubbleChartID3c7e5eba3096() {\n var pkgs \u003d window.__gvisPackages \u003d window.__gvisPackages || [];\n var callbacks \u003d window.__gvisCallbacks \u003d window.__gvisCallbacks || [];\n window.clearTimeout(window.__gvisLoad);\n // The timeout is set to 100 because otherwise the container div we are\n // targeting might not be part of the document yet\n window.__gvisLoad \u003d setTimeout(function() {\n var pkgCount \u003d pkgs.length;\n google.load(\"visualization\", \"1\", { packages:pkgs, callback: function() {\n if (pkgCount !\u003d pkgs.length) {\n // Race condition where another setTimeout call snuck in after us; if\n // that call added a package, we must not shift its callback\n return;\n}\nwhile (callbacks.length \u003e 0)\ncallbacks.shift()();\n} });\n}, 100);\n}\n \n// jsFooter\n\u003c/script\u003e\n \n\n\u003c!-- jsChart --\u003e \n\n\u003cscript type\u003d\"text/javascript\" src\u003d\"https://www.google.com/jsapi?callback\u003ddisplayChartBubbleChartID3c7e5eba3096\"\u003e\u003c/script\u003e\n \n\n\u003c!-- divChart --\u003e\n\n\u003cdiv id\u003d\"BubbleChartID3c7e5eba3096\" style\u003d\"width: 500; height: automatic;\"\u003e\n\u003c/div\u003e" }, - "dateCreated": "Feb 10, 2016 10:59:38 PM", - "dateStarted": "Feb 23, 2016 2:50:27 PM", - "dateFinished": "Feb 23, 2016 2:50:27 PM", + "dateCreated": "Feb 10, 2016 10:59:38 AM", + "dateStarted": "Feb 23, 2016 2:50:27 AM", + "dateFinished": "Feb 23, 2016 2:50:27 AM", "status": "ERROR", "progressUpdateIntervalMs": 500 }, { "text": "%r\nlibrary(googleVis)\ngeo \u003d gvisGeoChart(Exports, locationvar \u003d \"Country\", colorvar\u003d\"Profit\", options\u003dlist(Projection \u003d \"kavrayskiy-vii\"))\nprint(geo, tag \u003d \u0027chart\u0027)", - "dateUpdated": "Feb 23, 2016 2:50:29 PM", + "dateUpdated": "Feb 23, 2016 2:50:29 AM", "config": { "colWidth": 6.0, "graph": { @@ -893,6 +912,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455140544963_1486338978", "id": "20160210-224224_735421242", "result": { @@ -900,15 +920,15 @@ "type": "HTML", "msg": "\u003c!-- GeoChart generated in R 3.1.2 by googleVis 0.5.10 package --\u003e\n\n\u003c!-- Tue Feb 23 14:50:29 2016 --\u003e\n\n\u003c!-- jsHeader --\u003e\n\n\u003cscript type\u003d\"text/javascript\"\u003e\n \n// jsData \nfunction gvisDataGeoChartID3c7e62c8d602 () {\nvar data \u003d new google.visualization.DataTable();\nvar datajson \u003d\n[\n [\n \"Germany\",\n3 \n],\n[\n \"Brazil\",\n4 \n],\n[\n \"United States\",\n5 \n],\n[\n \"France\",\n4 \n],\n[\n \"Hungary\",\n3 \n],\n[\n \"India\",\n2 \n],\n[\n \"Iceland\",\n1 \n],\n[\n \"Norway\",\n4 \n],\n[\n \"Spain\",\n5 \n],\n[\n \"Turkey\",\n1 \n] \n];\ndata.addColumn(\u0027string\u0027,\u0027Country\u0027);\ndata.addColumn(\u0027number\u0027,\u0027Profit\u0027);\ndata.addRows(datajson);\nreturn(data);\n}\n \n// jsDrawChart\nfunction drawChartGeoChartID3c7e62c8d602() {\nvar data \u003d gvisDataGeoChartID3c7e62c8d602();\nvar options \u003d {};\noptions[\"width\"] \u003d 556;\noptions[\"height\"] \u003d 347;\noptions[\"Projection\"] \u003d \"kavrayskiy-vii\";\n\n var chart \u003d new google.visualization.GeoChart(\n document.getElementById(\u0027GeoChartID3c7e62c8d602\u0027)\n );\n chart.draw(data,options);\n \n\n}\n \n \n// jsDisplayChart\n(function() {\nvar pkgs \u003d window.__gvisPackages \u003d window.__gvisPackages || [];\nvar callbacks \u003d window.__gvisCallbacks \u003d window.__gvisCallbacks || [];\nvar chartid \u003d \"geochart\";\n \n// Manually see if chartid is in pkgs (not all browsers support Array.indexOf)\nvar i, newPackage \u003d true;\nfor (i \u003d 0; newPackage \u0026\u0026 i \u003c pkgs.length; i++) {\nif (pkgs[i] \u003d\u003d\u003d chartid)\nnewPackage \u003d false;\n}\nif (newPackage)\n pkgs.push(chartid);\n \n// Add the drawChart function to the global list of callbacks\ncallbacks.push(drawChartGeoChartID3c7e62c8d602);\n})();\nfunction displayChartGeoChartID3c7e62c8d602() {\n var pkgs \u003d window.__gvisPackages \u003d window.__gvisPackages || [];\n var callbacks \u003d window.__gvisCallbacks \u003d window.__gvisCallbacks || [];\n window.clearTimeout(window.__gvisLoad);\n // The timeout is set to 100 because otherwise the container div we are\n // targeting might not be part of the document yet\n window.__gvisLoad \u003d setTimeout(function() {\n var pkgCount \u003d pkgs.length;\n google.load(\"visualization\", \"1\", { packages:pkgs, callback: function() {\n if (pkgCount !\u003d pkgs.length) {\n // Race condition where another setTimeout call snuck in after us; if\n // that call added a package, we must not shift its callback\n return;\n}\nwhile (callbacks.length \u003e 0)\ncallbacks.shift()();\n} });\n}, 100);\n}\n \n// jsFooter\n\u003c/script\u003e\n \n\n\u003c!-- jsChart --\u003e \n\n\u003cscript type\u003d\"text/javascript\" src\u003d\"https://www.google.com/jsapi?callback\u003ddisplayChartGeoChartID3c7e62c8d602\"\u003e\u003c/script\u003e\n \n\n\u003c!-- divChart --\u003e\n\n\u003cdiv id\u003d\"GeoChartID3c7e62c8d602\" style\u003d\"width: 556; height: 347;\"\u003e\n\u003c/div\u003e" }, - "dateCreated": "Feb 10, 2016 10:42:24 PM", - "dateStarted": "Feb 23, 2016 2:50:29 PM", - "dateFinished": "Feb 23, 2016 2:50:29 PM", + "dateCreated": "Feb 10, 2016 10:42:24 AM", + "dateStarted": "Feb 23, 2016 2:50:29 AM", + "dateFinished": "Feb 23, 2016 2:50:29 AM", "status": "ERROR", "progressUpdateIntervalMs": 500 }, { "text": "%r\nrequire(rCharts)\ndata(economics, package \u003d \u0027ggplot2\u0027)\necon \u003c- transform(economics, date \u003d as.character(date))\nm1 \u003c- mPlot(x \u003d \u0027date\u0027, y \u003d c(\u0027psavert\u0027, \u0027uempmed\u0027), type \u003d \u0027Line\u0027, data \u003d econ)\nm1$set(pointSize \u003d 0, lineWidth \u003d 1)\nm1$show(\u0027inline\u0027, include_assets\u003dTRUE, cdn\u003dTRUE)", - "dateUpdated": "Feb 23, 2016 2:50:31 PM", + "dateUpdated": "Feb 23, 2016 2:50:31 AM", "config": { "colWidth": 6.0, "graph": { @@ -927,6 +947,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455130083870_1243919591", "id": "20160210-194803_744501428", "result": { @@ -934,15 +955,15 @@ "type": "HTML", "msg": "\u003cp\u003e\u003clink rel\u003d\"stylesheet\" href\u003d\"http://cdn.oesmith.co.uk/morris-0.4.2.min.css\" /\u003e\u003c/p\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js\"\u003e\u003c/script\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js\"\u003e\u003c/script\u003e\u003cscript type\u003d\"text/javascript\" src\u003d\"http://cdn.oesmith.co.uk/morris-0.4.2.min.js\"\u003e\u003c/script\u003e\u003cp\u003e\u003cstyle\u003e\n .rChart {\n display: block;\n margin-left: auto; \n margin-right: auto;\n width: 800px;\n height: 400px;\n }\u003cbr/\u003e\n \u003c/style\u003e\u003c/p\u003e\u003cdiv id\u003d\"chart3c7e79bc798\" class\u003d\"rChart morris\"\u003e\u003c/div\u003e\u003cscript type\u003d\"text/javascript\"\u003e\n var chartParams \u003d {\n \"element\": \"chart3c7e79bc798\",\n\"width\": 800,\n\"height\": 400,\n\"xkey\": \"date\",\n\"ykeys\": [\n \"psavert\",\n\"uempmed\" \n],\n\"data\": [\n {\n \"date\": \"1967-06-30\",\n\"pce\": 507.8,\n\"pop\": 198712,\n\"psavert\": 9.8,\n\"uempmed\": 4.5,\n\"unemploy\": 2944 \n},\n{\n \"date\": \"1967-07-31\",\n\"pce\": 510.9,\n\"pop\": 198911,\n\"psavert\": 9.8,\n\"uempmed\": 4.7,\n\"unemploy\": 2945 \n},\n{\n \"date\": \"1967-08-31\",\n\"pce\": 516.7,\n\"pop\": 199113,\n\"psavert\": 9,\n\"uempmed\": 4.6,\n\"unemploy\": 2958 \n},\n{\n \"date\": \"1967-09-30\",\n\"pce\": 513.3,\n\"pop\": 199311,\n\"psavert\": 9.8,\n\"uempmed\": 4.9,\n\"unemploy\": 3143 \n},\n{\n \"date\": \"1967-10-31\",\n\"pce\": 518.5,\n\"pop\": 199498,\n\"psavert\": 9.7,\n\"uempmed\": 4.7,\n\"unemploy\": 3066 \n},\n{\n \"date\": \"1967-11-30\",\n\"pce\": 526.2,\n\"pop\": 199657,\n\"psavert\": 9.4,\n\"uempmed\": 4.8,\n\"unemploy\": 3018 \n},\n{\n \"date\": \"1967-12-31\",\n\"pce\": 532,\n\"pop\": 199808,\n\"psavert\": 9,\n\"uempmed\": 5.1,\n\"unemploy\": 2878 \n},\n{\n \"date\": \"1968-01-31\",\n\"pce\": 534.7,\n\"pop\": 199920,\n\"psavert\": 9.5,\n\"uempmed\": 4.5,\n\"unemploy\": 3001 \n},\n{\n \"date\": \"1968-02-29\",\n\"pce\": 545.4,\n\"pop\": 200056,\n\"psavert\": 8.9,\n\"uempmed\": 4.1,\n\"unemploy\": 2877 \n},\n{\n \"date\": \"1968-03-31\",\n\"pce\": 545.1,\n\"pop\": 200208,\n\"psavert\": 9.6,\n\"uempmed\": 4.6,\n\"unemploy\": 2709 \n},\n{\n \"date\": \"1968-04-30\",\n\"pce\": 550.9,\n\"pop\": 200361,\n\"psavert\": 9.3,\n\"uempmed\": 4.4,\n\"unemploy\": 2740 \n},\n{\n \"date\": \"1968-05-31\",\n\"pce\": 557.4,\n\"pop\": 200536,\n\"psavert\": 8.9,\n\"uempmed\": 4.4,\n\"unemploy\": 2938 \n},\n{\n \"date\": \"1968-06-30\",\n\"pce\": 564.4,\n\"pop\": 200706,\n\"psavert\": 7.8,\n\"uempmed\": 4.5,\n\"unemploy\": 2883 \n},\n{\n \"date\": \"1968-07-31\",\n\"pce\": 568.2,\n\"pop\": 200898,\n\"psavert\": 7.6,\n\"uempmed\": 4.2,\n\"unemploy\": 2768 \n},\n{\n \"date\": \"1968-08-31\",\n\"pce\": 569.5,\n\"pop\": 201095,\n\"psavert\": 7.6,\n\"uempmed\": 4.6,\n\"unemploy\": 2686 \n},\n{\n \"date\": \"1968-09-30\",\n\"pce\": 572.9,\n\"pop\": 201290,\n\"psavert\": 7.8,\n\"uempmed\": 4.8,\n\"unemploy\": 2689 \n},\n{\n \"date\": \"1968-10-31\",\n\"pce\": 578,\n\"pop\": 201466,\n\"psavert\": 7.6,\n\"uempmed\": 4.4,\n\"unemploy\": 2715 \n},\n{\n \"date\": \"1968-11-30\",\n\"pce\": 577.9,\n\"pop\": 201621,\n\"psavert\": 8.1,\n\"uempmed\": 4.4,\n\"unemploy\": 2685 \n},\n{\n \"date\": \"1968-12-31\",\n\"pce\": 584.9,\n\"pop\": 201760,\n\"psavert\": 7.1,\n\"uempmed\": 4.4,\n\"unemploy\": 2718 \n},\n{\n \"date\": \"1969-01-31\",\n\"pce\": 590.2,\n\"pop\": 201881,\n\"psavert\": 6.5,\n\"uempmed\": 4.9,\n\"unemploy\": 2692 \n},\n{\n \"date\": \"1969-02-28\",\n\"pce\": 590.4,\n\"pop\": 202023,\n\"psavert\": 7,\n\"uempmed\": 4,\n\"unemploy\": 2712 \n},\n{\n \"date\": \"1969-03-31\",\n\"pce\": 595.4,\n\"pop\": 202161,\n\"psavert\": 6.6,\n\"uempmed\": 4,\n\"unemploy\": 2758 \n},\n{\n \"date\": \"1969-04-30\",\n\"pce\": 601.8,\n\"pop\": 202331,\n\"psavert\": 7,\n\"uempmed\": 4.2,\n\"unemploy\": 2713 \n},\n{\n \"date\": \"1969-05-31\",\n\"pce\": 602.4,\n\"pop\": 202507,\n\"psavert\": 7.9,\n\"uempmed\": 4.4,\n\"unemploy\": 2816 \n},\n{\n \"date\": \"1969-06-30\",\n\"pce\": 604.3,\n\"pop\": 202677,\n\"psavert\": 8.7,\n\"uempmed\": 4.4,\n\"unemploy\": 2868 \n},\n{\n \"date\": \"1969-07-31\",\n\"pce\": 611.5,\n\"pop\": 202877,\n\"psavert\": 8.5,\n\"uempmed\": 4.4,\n\"unemploy\": 2856 \n},\n{\n \"date\": \"1969-08-31\",\n\"pce\": 614.9,\n\"pop\": 203090,\n\"psavert\": 8.5,\n\"uempmed\": 4.7,\n\"unemploy\": 3040 \n},\n{\n \"date\": \"1969-09-30\",\n\"pce\": 620.2,\n\"pop\": 203302,\n\"psavert\": 8.3,\n\"uempmed\": 4.5,\n\"unemploy\": 3049 \n},\n{\n \"date\": \"1969-10-31\",\n\"pce\": 622.1,\n\"pop\": 203500,\n\"psavert\": 8.5,\n\"uempmed\": 4.8,\n\"unemploy\": 2856 \n},\n{\n \"date\": \"1969-11-30\",\n\"pce\": 624.4,\n\"pop\": 203675,\n\"psavert\": 8.6,\n\"uempmed\": 4.6,\n\"unemploy\": 2884 \n},\n{\n \"date\": \"1969-12-31\",\n\"pce\": 630.4,\n\"pop\": 203849,\n\"psavert\": 8.3,\n\"uempmed\": 4.6,\n\"unemploy\": 3201 \n},\n{\n \"date\": \"1970-01-31\",\n\"pce\": 635.7,\n\"pop\": 204008,\n\"psavert\": 8.1,\n\"uempmed\": 4.5,\n\"unemploy\": 3453 \n},\n{\n \"date\": \"1970-02-28\",\n\"pce\": 634,\n\"pop\": 204156,\n\"psavert\": 8.8,\n\"uempmed\": 4.6,\n\"unemploy\": 3635 \n},\n{\n \"date\": \"1970-03-31\",\n\"pce\": 637.7,\n\"pop\": 204401,\n\"psavert\": 10.5,\n\"uempmed\": 4.1,\n\"unemploy\": 3797 \n},\n{\n \"date\": \"1970-04-30\",\n\"pce\": 644.1,\n\"pop\": 204607,\n\"psavert\": 9.4,\n\"uempmed\": 4.7,\n\"unemploy\": 3919 \n},\n{\n \"date\": \"1970-05-31\",\n\"pce\": 648,\n\"pop\": 204830,\n\"psavert\": 8.7,\n\"uempmed\": 4.9,\n\"unemploy\": 4071 \n},\n{\n \"date\": \"1970-06-30\",\n\"pce\": 650.2,\n\"pop\": 205052,\n\"psavert\": 10,\n\"uempmed\": 5.1,\n\"unemploy\": 4175 \n},\n{\n \"date\": \"1970-07-31\",\n\"pce\": 654.7,\n\"pop\": 205295,\n\"psavert\": 10,\n\"uempmed\": 5.4,\n\"unemploy\": 4256 \n},\n{\n \"date\": \"1970-08-31\",\n\"pce\": 660.9,\n\"pop\": 205540,\n\"psavert\": 9.8,\n\"uempmed\": 5.2,\n\"unemploy\": 4456 \n},\n{\n \"date\": \"1970-09-30\",\n\"pce\": 660.1,\n\"pop\": 205788,\n\"psavert\": 9.8,\n\"uempmed\": 5.2,\n\"unemploy\": 4591 \n},\n{\n \"date\": \"1970-10-31\",\n\"pce\": 658.4,\n\"pop\": 206024,\n\"psavert\": 10.1,\n\"uempmed\": 5.6,\n\"unemploy\": 4898 \n},\n{\n \"date\": \"1970-11-30\",\n\"pce\": 667.4,\n\"pop\": 206238,\n\"psavert\": 9.7,\n\"uempmed\": 5.9,\n\"unemploy\": 5076 \n},\n{\n \"date\": \"1970-12-31\",\n\"pce\": 678,\n\"pop\": 206466,\n\"psavert\": 10,\n\"uempmed\": 6.2,\n\"unemploy\": 4986 \n},\n{\n \"date\": \"1971-01-31\",\n\"pce\": 681.3,\n\"pop\": 206668,\n\"psavert\": 9.9,\n\"uempmed\": 6.3,\n\"unemploy\": 4903 \n},\n{\n \"date\": \"1971-02-28\",\n\"pce\": 683.9,\n\"pop\": 206855,\n\"psavert\": 10.2,\n\"uempmed\": 6.4,\n\"unemploy\": 4987 \n},\n{\n \"date\": \"1971-03-31\",\n\"pce\": 690.6,\n\"pop\": 207065,\n\"psavert\": 9.9,\n\"uempmed\": 6.5,\n\"unemploy\": 4959 \n},\n{\n \"date\": \"1971-04-30\",\n\"pce\": 693,\n\"pop\": 207260,\n\"psavert\": 10.2,\n\"uempmed\": 6.7,\n\"unemploy\": 4996 \n},\n{\n \"date\": \"1971-05-31\",\n\"pce\": 701.7,\n\"pop\": 207462,\n\"psavert\": 11.4,\n\"uempmed\": 5.7,\n\"unemploy\": 4949 \n},\n{\n \"date\": \"1971-06-30\",\n\"pce\": 700.8,\n\"pop\": 207661,\n\"psavert\": 10.4,\n\"uempmed\": 6.2,\n\"unemploy\": 5035 \n},\n{\n \"date\": \"1971-07-31\",\n\"pce\": 706.8,\n\"pop\": 207881,\n\"psavert\": 10.3,\n\"uempmed\": 6.4,\n\"unemploy\": 5134 \n},\n{\n \"date\": \"1971-08-31\",\n\"pce\": 715,\n\"pop\": 208114,\n\"psavert\": 9.7,\n\"uempmed\": 5.8,\n\"unemploy\": 5042 \n},\n{\n \"date\": \"1971-09-30\",\n\"pce\": 717.8,\n\"pop\": 208345,\n\"psavert\": 9.6,\n\"uempmed\": 6.5,\n\"unemploy\": 4954 \n},\n{\n \"date\": \"1971-10-31\",\n\"pce\": 723,\n\"pop\": 208555,\n\"psavert\": 9.5,\n\"uempmed\": 6.4,\n\"unemploy\": 5161 \n},\n{\n \"date\": \"1971-11-30\",\n\"pce\": 730.5,\n\"pop\": 208740,\n\"psavert\": 9.5,\n\"uempmed\": 6.2,\n\"unemploy\": 5154 \n},\n{\n \"date\": \"1971-12-31\",\n\"pce\": 733.7,\n\"pop\": 208917,\n\"psavert\": 9.1,\n\"uempmed\": 6.2,\n\"unemploy\": 5019 \n},\n{\n \"date\": \"1972-01-31\",\n\"pce\": 738.4,\n\"pop\": 209061,\n\"psavert\": 9.4,\n\"uempmed\": 6.6,\n\"unemploy\": 4928 \n},\n{\n \"date\": \"1972-02-29\",\n\"pce\": 751.5,\n\"pop\": 209212,\n\"psavert\": 8.2,\n\"uempmed\": 6.6,\n\"unemploy\": 5038 \n},\n{\n \"date\": \"1972-03-31\",\n\"pce\": 754.9,\n\"pop\": 209386,\n\"psavert\": 8.3,\n\"uempmed\": 6.7,\n\"unemploy\": 4959 \n},\n{\n \"date\": \"1972-04-30\",\n\"pce\": 760.4,\n\"pop\": 209545,\n\"psavert\": 8.5,\n\"uempmed\": 6.6,\n\"unemploy\": 4922 \n},\n{\n \"date\": \"1972-05-31\",\n\"pce\": 764,\n\"pop\": 209725,\n\"psavert\": 7.2,\n\"uempmed\": 5.4,\n\"unemploy\": 4923 \n},\n{\n \"date\": \"1972-06-30\",\n\"pce\": 772.4,\n\"pop\": 209896,\n\"psavert\": 8.2,\n\"uempmed\": 6.1,\n\"unemploy\": 4913 \n},\n{\n \"date\": \"1972-07-31\",\n\"pce\": 778.9,\n\"pop\": 210075,\n\"psavert\": 8.6,\n\"uempmed\": 6,\n\"unemploy\": 4939 \n},\n{\n \"date\": \"1972-08-31\",\n\"pce\": 783.7,\n\"pop\": 210278,\n\"psavert\": 8.8,\n\"uempmed\": 5.6,\n\"unemploy\": 4849 \n},\n{\n \"date\": \"1972-09-30\",\n\"pce\": 797.5,\n\"pop\": 210479,\n\"psavert\": 9.5,\n\"uempmed\": 5.7,\n\"unemploy\": 4875 \n},\n{\n \"date\": \"1972-10-31\",\n\"pce\": 803.1,\n\"pop\": 210656,\n\"psavert\": 10.2,\n\"uempmed\": 5.7,\n\"unemploy\": 4602 \n},\n{\n \"date\": \"1972-11-30\",\n\"pce\": 808.8,\n\"pop\": 210821,\n\"psavert\": 10.3,\n\"uempmed\": 6.1,\n\"unemploy\": 4543 \n},\n{\n \"date\": \"1972-12-31\",\n\"pce\": 819.1,\n\"pop\": 210985,\n\"psavert\": 9.1,\n\"uempmed\": 5.7,\n\"unemploy\": 4326 \n},\n{\n \"date\": \"1973-01-31\",\n\"pce\": 828.5,\n\"pop\": 211120,\n\"psavert\": 9.5,\n\"uempmed\": 5.2,\n\"unemploy\": 4452 \n},\n{\n \"date\": \"1973-02-28\",\n\"pce\": 835.5,\n\"pop\": 211254,\n\"psavert\": 9.7,\n\"uempmed\": 5.5,\n\"unemploy\": 4394 \n},\n{\n \"date\": \"1973-03-31\",\n\"pce\": 838.5,\n\"pop\": 211420,\n\"psavert\": 10,\n\"uempmed\": 5,\n\"unemploy\": 4459 \n},\n{\n \"date\": \"1973-04-30\",\n\"pce\": 844.3,\n\"pop\": 211577,\n\"psavert\": 10.2,\n\"uempmed\": 4.9,\n\"unemploy\": 4329 \n},\n{\n \"date\": \"1973-05-31\",\n\"pce\": 847.1,\n\"pop\": 211746,\n\"psavert\": 10.7,\n\"uempmed\": 5,\n\"unemploy\": 4363 \n},\n{\n \"date\": \"1973-06-30\",\n\"pce\": 857,\n\"pop\": 211909,\n\"psavert\": 10.2,\n\"uempmed\": 5.2,\n\"unemploy\": 4305 \n},\n{\n \"date\": \"1973-07-31\",\n\"pce\": 856.1,\n\"pop\": 212092,\n\"psavert\": 11,\n\"uempmed\": 4.9,\n\"unemploy\": 4305 \n},\n{\n \"date\": \"1973-08-31\",\n\"pce\": 872.2,\n\"pop\": 212289,\n\"psavert\": 10.2,\n\"uempmed\": 5.4,\n\"unemploy\": 4350 \n},\n{\n \"date\": \"1973-09-30\",\n\"pce\": 871.2,\n\"pop\": 212475,\n\"psavert\": 11.5,\n\"uempmed\": 5.5,\n\"unemploy\": 4144 \n},\n{\n \"date\": \"1973-10-31\",\n\"pce\": 879.9,\n\"pop\": 212634,\n\"psavert\": 11.6,\n\"uempmed\": 5.1,\n\"unemploy\": 4396 \n},\n{\n \"date\": \"1973-11-30\",\n\"pce\": 879.7,\n\"pop\": 212785,\n\"psavert\": 12,\n\"uempmed\": 4.7,\n\"unemploy\": 4489 \n},\n{\n \"date\": \"1973-12-31\",\n\"pce\": 887.7,\n\"pop\": 212932,\n\"psavert\": 11.6,\n\"uempmed\": 5,\n\"unemploy\": 4644 \n},\n{\n \"date\": \"1974-01-31\",\n\"pce\": 892.9,\n\"pop\": 213074,\n\"psavert\": 11.4,\n\"uempmed\": 5.1,\n\"unemploy\": 4731 \n},\n{\n \"date\": \"1974-02-28\",\n\"pce\": 904.7,\n\"pop\": 213211,\n\"psavert\": 10.6,\n\"uempmed\": 4.8,\n\"unemploy\": 4634 \n},\n{\n \"date\": \"1974-03-31\",\n\"pce\": 914.1,\n\"pop\": 213361,\n\"psavert\": 10.2,\n\"uempmed\": 5,\n\"unemploy\": 4618 \n},\n{\n \"date\": \"1974-04-30\",\n\"pce\": 925.7,\n\"pop\": 213513,\n\"psavert\": 10,\n\"uempmed\": 4.6,\n\"unemploy\": 4705 \n},\n{\n \"date\": \"1974-05-31\",\n\"pce\": 931.3,\n\"pop\": 213686,\n\"psavert\": 10.2,\n\"uempmed\": 5.3,\n\"unemploy\": 4927 \n},\n{\n \"date\": \"1974-06-30\",\n\"pce\": 941.2,\n\"pop\": 213854,\n\"psavert\": 10.6,\n\"uempmed\": 5.7,\n\"unemploy\": 5063 \n},\n{\n \"date\": \"1974-07-31\",\n\"pce\": 958,\n\"pop\": 214042,\n\"psavert\": 9.5,\n\"uempmed\": 5,\n\"unemploy\": 5022 \n},\n{\n \"date\": \"1974-08-31\",\n\"pce\": 958.3,\n\"pop\": 214246,\n\"psavert\": 10.2,\n\"uempmed\": 5.3,\n\"unemploy\": 5437 \n},\n{\n \"date\": \"1974-09-30\",\n\"pce\": 962.5,\n\"pop\": 214451,\n\"psavert\": 10.7,\n\"uempmed\": 5.5,\n\"unemploy\": 5523 \n},\n{\n \"date\": \"1974-10-31\",\n\"pce\": 959.5,\n\"pop\": 214625,\n\"psavert\": 11.1,\n\"uempmed\": 5.2,\n\"unemploy\": 6140 \n},\n{\n \"date\": \"1974-11-30\",\n\"pce\": 965.1,\n\"pop\": 214782,\n\"psavert\": 11.1,\n\"uempmed\": 5.7,\n\"unemploy\": 6636 \n},\n{\n \"date\": \"1974-12-31\",\n\"pce\": 978.9,\n\"pop\": 214931,\n\"psavert\": 10.3,\n\"uempmed\": 6.3,\n\"unemploy\": 7501 \n},\n{\n \"date\": \"1975-01-31\",\n\"pce\": 992.8,\n\"pop\": 215065,\n\"psavert\": 9.5,\n\"uempmed\": 7.1,\n\"unemploy\": 7520 \n},\n{\n \"date\": \"1975-02-28\",\n\"pce\": 994.1,\n\"pop\": 215198,\n\"psavert\": 9.7,\n\"uempmed\": 7.2,\n\"unemploy\": 7978 \n},\n{\n \"date\": \"1975-03-31\",\n\"pce\": 998.8,\n\"pop\": 215353,\n\"psavert\": 11.3,\n\"uempmed\": 8.7,\n\"unemploy\": 8210 \n},\n{\n \"date\": \"1975-04-30\",\n\"pce\": 1022.8,\n\"pop\": 215523,\n\"psavert\": 14.6,\n\"uempmed\": 9.4,\n\"unemploy\": 8433 \n},\n{\n \"date\": \"1975-05-31\",\n\"pce\": 1030.7,\n\"pop\": 215768,\n\"psavert\": 11.4,\n\"uempmed\": 8.8,\n\"unemploy\": 8220 \n},\n{\n \"date\": \"1975-06-30\",\n\"pce\": 1043.8,\n\"pop\": 215973,\n\"psavert\": 9.7,\n\"uempmed\": 8.6,\n\"unemploy\": 8127 \n},\n{\n \"date\": \"1975-07-31\",\n\"pce\": 1051,\n\"pop\": 216195,\n\"psavert\": 10.1,\n\"uempmed\": 9.2,\n\"unemploy\": 7928 \n},\n{\n \"date\": \"1975-08-31\",\n\"pce\": 1058.9,\n\"pop\": 216393,\n\"psavert\": 10.2,\n\"uempmed\": 9.2,\n\"unemploy\": 7923 \n},\n{\n \"date\": \"1975-09-30\",\n\"pce\": 1064.8,\n\"pop\": 216587,\n\"psavert\": 10.7,\n\"uempmed\": 8.6,\n\"unemploy\": 7897 \n},\n{\n \"date\": \"1975-10-31\",\n\"pce\": 1079.7,\n\"pop\": 216771,\n\"psavert\": 10,\n\"uempmed\": 9.5,\n\"unemploy\": 7794 \n},\n{\n \"date\": \"1975-11-30\",\n\"pce\": 1096,\n\"pop\": 216931,\n\"psavert\": 9.3,\n\"uempmed\": 9,\n\"unemploy\": 7744 \n},\n{\n \"date\": \"1975-12-31\",\n\"pce\": 1111.2,\n\"pop\": 217095,\n\"psavert\": 9.2,\n\"uempmed\": 9,\n\"unemploy\": 7534 \n},\n{\n \"date\": \"1976-01-31\",\n\"pce\": 1111.8,\n\"pop\": 217249,\n\"psavert\": 9.9,\n\"uempmed\": 8.2,\n\"unemploy\": 7326 \n},\n{\n \"date\": \"1976-02-29\",\n\"pce\": 1119,\n\"pop\": 217381,\n\"psavert\": 9.8,\n\"uempmed\": 8.7,\n\"unemploy\": 7230 \n},\n{\n \"date\": \"1976-03-31\",\n\"pce\": 1129.6,\n\"pop\": 217528,\n\"psavert\": 9.4,\n\"uempmed\": 8.2,\n\"unemploy\": 7330 \n},\n{\n \"date\": \"1976-04-30\",\n\"pce\": 1126.8,\n\"pop\": 217685,\n\"psavert\": 10.1,\n\"uempmed\": 8.3,\n\"unemploy\": 7053 \n},\n{\n \"date\": \"1976-05-31\",\n\"pce\": 1144.7,\n\"pop\": 217861,\n\"psavert\": 9.2,\n\"uempmed\": 7.8,\n\"unemploy\": 7322 \n},\n{\n \"date\": \"1976-06-30\",\n\"pce\": 1153.8,\n\"pop\": 218035,\n\"psavert\": 9.5,\n\"uempmed\": 7.7,\n\"unemploy\": 7490 \n},\n{\n \"date\": \"1976-07-31\",\n\"pce\": 1162.3,\n\"pop\": 218233,\n\"psavert\": 9.6,\n\"uempmed\": 7.9,\n\"unemploy\": 7518 \n},\n{\n \"date\": \"1976-08-31\",\n\"pce\": 1173.2,\n\"pop\": 218440,\n\"psavert\": 9.3,\n\"uempmed\": 7.8,\n\"unemploy\": 7380 \n},\n{\n \"date\": \"1976-09-30\",\n\"pce\": 1181.2,\n\"pop\": 218644,\n\"psavert\": 9,\n\"uempmed\": 7.7,\n\"unemploy\": 7430 \n},\n{\n \"date\": \"1976-10-31\",\n\"pce\": 1193.5,\n\"pop\": 218834,\n\"psavert\": 9.4,\n\"uempmed\": 8.4,\n\"unemploy\": 7620 \n},\n{\n \"date\": \"1976-11-30\",\n\"pce\": 1216,\n\"pop\": 219006,\n\"psavert\": 8.4,\n\"uempmed\": 8,\n\"unemploy\": 7545 \n},\n{\n \"date\": \"1976-12-31\",\n\"pce\": 1219.3,\n\"pop\": 219179,\n\"psavert\": 8.5,\n\"uempmed\": 7.5,\n\"unemploy\": 7280 \n},\n{\n \"date\": \"1977-01-31\",\n\"pce\": 1235.6,\n\"pop\": 219344,\n\"psavert\": 7.1,\n\"uempmed\": 7.2,\n\"unemploy\": 7443 \n},\n{\n \"date\": \"1977-02-28\",\n\"pce\": 1242.6,\n\"pop\": 219504,\n\"psavert\": 8.4,\n\"uempmed\": 7.2,\n\"unemploy\": 7307 \n},\n{\n \"date\": \"1977-03-31\",\n\"pce\": 1251.6,\n\"pop\": 219684,\n\"psavert\": 8.4,\n\"uempmed\": 7.3,\n\"unemploy\": 7059 \n},\n{\n \"date\": \"1977-04-30\",\n\"pce\": 1261.5,\n\"pop\": 219859,\n\"psavert\": 8.3,\n\"uempmed\": 7.9,\n\"unemploy\": 6911 \n},\n{\n \"date\": \"1977-05-31\",\n\"pce\": 1268.2,\n\"pop\": 220046,\n\"psavert\": 8.7,\n\"uempmed\": 6.2,\n\"unemploy\": 7134 \n},\n{\n \"date\": \"1977-06-30\",\n\"pce\": 1285.2,\n\"pop\": 220239,\n\"psavert\": 8.6,\n\"uempmed\": 7.1,\n\"unemploy\": 6829 \n},\n{\n \"date\": \"1977-07-31\",\n\"pce\": 1290.4,\n\"pop\": 220458,\n\"psavert\": 9,\n\"uempmed\": 7,\n\"unemploy\": 6925 \n},\n{\n \"date\": \"1977-08-31\",\n\"pce\": 1299.4,\n\"pop\": 220688,\n\"psavert\": 9.3,\n\"uempmed\": 6.7,\n\"unemploy\": 6751 \n},\n{\n \"date\": \"1977-09-30\",\n\"pce\": 1316.3,\n\"pop\": 220904,\n\"psavert\": 9.4,\n\"uempmed\": 6.9,\n\"unemploy\": 6763 \n},\n{\n \"date\": \"1977-10-31\",\n\"pce\": 1332,\n\"pop\": 221109,\n\"psavert\": 9.4,\n\"uempmed\": 7,\n\"unemploy\": 6815 \n},\n{\n \"date\": \"1977-11-30\",\n\"pce\": 1341.3,\n\"pop\": 221303,\n\"psavert\": 9.4,\n\"uempmed\": 6.8,\n\"unemploy\": 6386 \n},\n{\n \"date\": \"1977-12-31\",\n\"pce\": 1335.2,\n\"pop\": 221477,\n\"psavert\": 9.9,\n\"uempmed\": 6.5,\n\"unemploy\": 6489 \n},\n{\n \"date\": \"1978-01-31\",\n\"pce\": 1361,\n\"pop\": 221629,\n\"psavert\": 9.1,\n\"uempmed\": 6.7,\n\"unemploy\": 6318 \n},\n{\n \"date\": \"1978-02-28\",\n\"pce\": 1383.6,\n\"pop\": 221792,\n\"psavert\": 9.1,\n\"uempmed\": 6.2,\n\"unemploy\": 6337 \n},\n{\n \"date\": \"1978-03-31\",\n\"pce\": 1402.5,\n\"pop\": 221991,\n\"psavert\": 8.9,\n\"uempmed\": 6.1,\n\"unemploy\": 6180 \n},\n{\n \"date\": \"1978-04-30\",\n\"pce\": 1418.2,\n\"pop\": 222176,\n\"psavert\": 8.5,\n\"uempmed\": 5.7,\n\"unemploy\": 6127 \n},\n{\n \"date\": \"1978-05-31\",\n\"pce\": 1432.1,\n\"pop\": 222379,\n\"psavert\": 8.1,\n\"uempmed\": 6,\n\"unemploy\": 6028 \n},\n{\n \"date\": \"1978-06-30\",\n\"pce\": 1433.2,\n\"pop\": 222585,\n\"psavert\": 9.1,\n\"uempmed\": 5.8,\n\"unemploy\": 6309 \n},\n{\n \"date\": \"1978-07-31\",\n\"pce\": 1453.4,\n\"pop\": 222805,\n\"psavert\": 8.5,\n\"uempmed\": 5.8,\n\"unemploy\": 6080 \n},\n{\n \"date\": \"1978-08-31\",\n\"pce\": 1459.4,\n\"pop\": 223053,\n\"psavert\": 8.8,\n\"uempmed\": 5.6,\n\"unemploy\": 6125 \n},\n{\n \"date\": \"1978-09-30\",\n\"pce\": 1473.5,\n\"pop\": 223271,\n\"psavert\": 8.9,\n\"uempmed\": 5.9,\n\"unemploy\": 5947 \n},\n{\n \"date\": \"1978-10-31\",\n\"pce\": 1487.1,\n\"pop\": 223477,\n\"psavert\": 8.8,\n\"uempmed\": 5.5,\n\"unemploy\": 6077 \n},\n{\n \"date\": \"1978-11-30\",\n\"pce\": 1503,\n\"pop\": 223670,\n\"psavert\": 8.7,\n\"uempmed\": 5.6,\n\"unemploy\": 6228 \n},\n{\n \"date\": \"1978-12-31\",\n\"pce\": 1508.9,\n\"pop\": 223865,\n\"psavert\": 9.4,\n\"uempmed\": 5.9,\n\"unemploy\": 6109 \n},\n{\n \"date\": \"1979-01-31\",\n\"pce\": 1524.4,\n\"pop\": 224053,\n\"psavert\": 9.3,\n\"uempmed\": 5.9,\n\"unemploy\": 6173 \n},\n{\n \"date\": \"1979-02-28\",\n\"pce\": 1537.7,\n\"pop\": 224235,\n\"psavert\": 9.5,\n\"uempmed\": 5.9,\n\"unemploy\": 6109 \n},\n{\n \"date\": \"1979-03-31\",\n\"pce\": 1545.1,\n\"pop\": 224438,\n\"psavert\": 9.2,\n\"uempmed\": 5.4,\n\"unemploy\": 6069 \n},\n{\n \"date\": \"1979-04-30\",\n\"pce\": 1565.5,\n\"pop\": 224632,\n\"psavert\": 8.8,\n\"uempmed\": 5.6,\n\"unemploy\": 5840 \n},\n{\n \"date\": \"1979-05-31\",\n\"pce\": 1582.3,\n\"pop\": 224843,\n\"psavert\": 8.4,\n\"uempmed\": 5.6,\n\"unemploy\": 5959 \n},\n{\n \"date\": \"1979-06-30\",\n\"pce\": 1592.6,\n\"pop\": 225055,\n\"psavert\": 9.1,\n\"uempmed\": 5.9,\n\"unemploy\": 5996 \n},\n{\n \"date\": \"1979-07-31\",\n\"pce\": 1622.3,\n\"pop\": 225295,\n\"psavert\": 8.3,\n\"uempmed\": 4.8,\n\"unemploy\": 6320 \n},\n{\n \"date\": \"1979-08-31\",\n\"pce\": 1640.8,\n\"pop\": 225547,\n\"psavert\": 7.9,\n\"uempmed\": 5.5,\n\"unemploy\": 6190 \n},\n{\n \"date\": \"1979-09-30\",\n\"pce\": 1648.7,\n\"pop\": 225801,\n\"psavert\": 8.7,\n\"uempmed\": 5.5,\n\"unemploy\": 6296 \n},\n{\n \"date\": \"1979-10-31\",\n\"pce\": 1664.5,\n\"pop\": 226027,\n\"psavert\": 8.8,\n\"uempmed\": 5.3,\n\"unemploy\": 6238 \n},\n{\n \"date\": \"1979-11-30\",\n\"pce\": 1673.5,\n\"pop\": 226243,\n\"psavert\": 9.3,\n\"uempmed\": 5.7,\n\"unemploy\": 6325 \n},\n{\n \"date\": \"1979-12-31\",\n\"pce\": 1704.1,\n\"pop\": 226451,\n\"psavert\": 9.3,\n\"uempmed\": 5.3,\n\"unemploy\": 6683 \n},\n{\n \"date\": \"1980-01-31\",\n\"pce\": 1708.2,\n\"pop\": 226656,\n\"psavert\": 9.6,\n\"uempmed\": 5.8,\n\"unemploy\": 6702 \n},\n{\n \"date\": \"1980-02-29\",\n\"pce\": 1714.9,\n\"pop\": 226849,\n\"psavert\": 9.7,\n\"uempmed\": 6,\n\"unemploy\": 6729 \n},\n{\n \"date\": \"1980-03-31\",\n\"pce\": 1701.8,\n\"pop\": 227061,\n\"psavert\": 10.1,\n\"uempmed\": 5.8,\n\"unemploy\": 7358 \n},\n{\n \"date\": \"1980-04-30\",\n\"pce\": 1706.6,\n\"pop\": 227251,\n\"psavert\": 10,\n\"uempmed\": 5.7,\n\"unemploy\": 7984 \n},\n{\n \"date\": \"1980-05-31\",\n\"pce\": 1725.3,\n\"pop\": 227522,\n\"psavert\": 9.7,\n\"uempmed\": 6.4,\n\"unemploy\": 8098 \n},\n{\n \"date\": \"1980-06-30\",\n\"pce\": 1753.6,\n\"pop\": 227726,\n\"psavert\": 9.8,\n\"uempmed\": 7,\n\"unemploy\": 8363 \n},\n{\n \"date\": \"1980-07-31\",\n\"pce\": 1770.1,\n\"pop\": 227953,\n\"psavert\": 9.8,\n\"uempmed\": 7.5,\n\"unemploy\": 8281 \n},\n{\n \"date\": \"1980-08-31\",\n\"pce\": 1786.6,\n\"pop\": 228186,\n\"psavert\": 10.3,\n\"uempmed\": 7.7,\n\"unemploy\": 8021 \n},\n{\n \"date\": \"1980-09-30\",\n\"pce\": 1823,\n\"pop\": 228417,\n\"psavert\": 10.4,\n\"uempmed\": 7.5,\n\"unemploy\": 8088 \n},\n{\n \"date\": \"1980-10-31\",\n\"pce\": 1833,\n\"pop\": 228612,\n\"psavert\": 10.9,\n\"uempmed\": 7.7,\n\"unemploy\": 8023 \n},\n{\n \"date\": \"1980-11-30\",\n\"pce\": 1858.3,\n\"pop\": 228779,\n\"psavert\": 10.7,\n\"uempmed\": 7.5,\n\"unemploy\": 7718 \n},\n{\n \"date\": \"1980-12-31\",\n\"pce\": 1877.7,\n\"pop\": 228937,\n\"psavert\": 9.9,\n\"uempmed\": 7.4,\n\"unemploy\": 8071 \n},\n{\n \"date\": \"1981-01-31\",\n\"pce\": 1892.2,\n\"pop\": 229071,\n\"psavert\": 9.8,\n\"uempmed\": 7.1,\n\"unemploy\": 8051 \n},\n{\n \"date\": \"1981-02-28\",\n\"pce\": 1911.3,\n\"pop\": 229224,\n\"psavert\": 9.7,\n\"uempmed\": 7.1,\n\"unemploy\": 7982 \n},\n{\n \"date\": \"1981-03-31\",\n\"pce\": 1912.6,\n\"pop\": 229403,\n\"psavert\": 9.8,\n\"uempmed\": 7.4,\n\"unemploy\": 7869 \n},\n{\n \"date\": \"1981-04-30\",\n\"pce\": 1921.7,\n\"pop\": 229575,\n\"psavert\": 10,\n\"uempmed\": 6.9,\n\"unemploy\": 8174 \n},\n{\n \"date\": \"1981-05-31\",\n\"pce\": 1942.3,\n\"pop\": 229761,\n\"psavert\": 9.9,\n\"uempmed\": 6.6,\n\"unemploy\": 8098 \n},\n{\n \"date\": \"1981-06-30\",\n\"pce\": 1949.6,\n\"pop\": 229966,\n\"psavert\": 11.4,\n\"uempmed\": 7.1,\n\"unemploy\": 7863 \n},\n{\n \"date\": \"1981-07-31\",\n\"pce\": 1973.7,\n\"pop\": 230187,\n\"psavert\": 11.2,\n\"uempmed\": 7.2,\n\"unemploy\": 8036 \n},\n{\n \"date\": \"1981-08-31\",\n\"pce\": 1972.1,\n\"pop\": 230412,\n\"psavert\": 11.7,\n\"uempmed\": 6.8,\n\"unemploy\": 8230 \n},\n{\n \"date\": \"1981-09-30\",\n\"pce\": 1970,\n\"pop\": 230641,\n\"psavert\": 12.5,\n\"uempmed\": 6.8,\n\"unemploy\": 8646 \n},\n{\n \"date\": \"1981-10-31\",\n\"pce\": 1976,\n\"pop\": 230822,\n\"psavert\": 12.5,\n\"uempmed\": 6.9,\n\"unemploy\": 9029 \n},\n{\n \"date\": \"1981-11-30\",\n\"pce\": 1993.6,\n\"pop\": 230989,\n\"psavert\": 11.7,\n\"uempmed\": 6.9,\n\"unemploy\": 9267 \n},\n{\n \"date\": \"1981-12-31\",\n\"pce\": 2001.1,\n\"pop\": 231157,\n\"psavert\": 11.9,\n\"uempmed\": 7.1,\n\"unemploy\": 9397 \n},\n{\n \"date\": \"1982-01-31\",\n\"pce\": 2024.9,\n\"pop\": 231313,\n\"psavert\": 11.3,\n\"uempmed\": 7.5,\n\"unemploy\": 9705 \n},\n{\n \"date\": \"1982-02-28\",\n\"pce\": 2028.1,\n\"pop\": 231470,\n\"psavert\": 11.5,\n\"uempmed\": 7.7,\n\"unemploy\": 9895 \n},\n{\n \"date\": \"1982-03-31\",\n\"pce\": 2030.5,\n\"pop\": 231645,\n\"psavert\": 12.2,\n\"uempmed\": 8.1,\n\"unemploy\": 10244 \n},\n{\n \"date\": \"1982-04-30\",\n\"pce\": 2049.3,\n\"pop\": 231809,\n\"psavert\": 11.6,\n\"uempmed\": 8.5,\n\"unemploy\": 10335 \n},\n{\n \"date\": \"1982-05-31\",\n\"pce\": 2053.5,\n\"pop\": 231992,\n\"psavert\": 11.5,\n\"uempmed\": 9.5,\n\"unemploy\": 10538 \n},\n{\n \"date\": \"1982-06-30\",\n\"pce\": 2078.3,\n\"pop\": 232188,\n\"psavert\": 11.9,\n\"uempmed\": 8.5,\n\"unemploy\": 10849 \n},\n{\n \"date\": \"1982-07-31\",\n\"pce\": 2086.9,\n\"pop\": 232392,\n\"psavert\": 11.7,\n\"uempmed\": 8.7,\n\"unemploy\": 10881 \n},\n{\n \"date\": \"1982-08-31\",\n\"pce\": 2112,\n\"pop\": 232599,\n\"psavert\": 10.8,\n\"uempmed\": 9.5,\n\"unemploy\": 11217 \n},\n{\n \"date\": \"1982-09-30\",\n\"pce\": 2133.8,\n\"pop\": 232816,\n\"psavert\": 10.3,\n\"uempmed\": 9.7,\n\"unemploy\": 11529 \n},\n{\n \"date\": \"1982-10-31\",\n\"pce\": 2158.1,\n\"pop\": 232993,\n\"psavert\": 9.9,\n\"uempmed\": 10,\n\"unemploy\": 11938 \n},\n{\n \"date\": \"1982-11-30\",\n\"pce\": 2170.8,\n\"pop\": 233160,\n\"psavert\": 9.7,\n\"uempmed\": 10.2,\n\"unemploy\": 12051 \n},\n{\n \"date\": \"1982-12-31\",\n\"pce\": 2183.6,\n\"pop\": 233322,\n\"psavert\": 9.9,\n\"uempmed\": 11.1,\n\"unemploy\": 11534 \n},\n{\n \"date\": \"1983-01-31\",\n\"pce\": 2186.5,\n\"pop\": 233473,\n\"psavert\": 10,\n\"uempmed\": 9.8,\n\"unemploy\": 11545 \n},\n{\n \"date\": \"1983-02-28\",\n\"pce\": 2212.2,\n\"pop\": 233613,\n\"psavert\": 9.5,\n\"uempmed\": 10.4,\n\"unemploy\": 11408 \n},\n{\n \"date\": \"1983-03-31\",\n\"pce\": 2235.3,\n\"pop\": 233781,\n\"psavert\": 9.1,\n\"uempmed\": 10.9,\n\"unemploy\": 11268 \n},\n{\n \"date\": \"1983-04-30\",\n\"pce\": 2254.7,\n\"pop\": 233922,\n\"psavert\": 8.9,\n\"uempmed\": 12.3,\n\"unemploy\": 11154 \n},\n{\n \"date\": \"1983-05-31\",\n\"pce\": 2284.7,\n\"pop\": 234118,\n\"psavert\": 8.1,\n\"uempmed\": 11.3,\n\"unemploy\": 11246 \n},\n{\n \"date\": \"1983-06-30\",\n\"pce\": 2313.2,\n\"pop\": 234307,\n\"psavert\": 8.6,\n\"uempmed\": 10.1,\n\"unemploy\": 10548 \n},\n{\n \"date\": \"1983-07-31\",\n\"pce\": 2329.2,\n\"pop\": 234501,\n\"psavert\": 8,\n\"uempmed\": 9.3,\n\"unemploy\": 10623 \n},\n{\n \"date\": \"1983-08-31\",\n\"pce\": 2343.4,\n\"pop\": 234701,\n\"psavert\": 8.5,\n\"uempmed\": 9.3,\n\"unemploy\": 10282 \n},\n{\n \"date\": \"1983-09-30\",\n\"pce\": 2366.2,\n\"pop\": 234907,\n\"psavert\": 8.6,\n\"uempmed\": 9.4,\n\"unemploy\": 9887 \n},\n{\n \"date\": \"1983-10-31\",\n\"pce\": 2375,\n\"pop\": 235078,\n\"psavert\": 9.2,\n\"uempmed\": 9.3,\n\"unemploy\": 9499 \n},\n{\n \"date\": \"1983-11-30\",\n\"pce\": 2402.7,\n\"pop\": 235235,\n\"psavert\": 9.1,\n\"uempmed\": 8.7,\n\"unemploy\": 9331 \n},\n{\n \"date\": \"1983-12-31\",\n\"pce\": 2428.6,\n\"pop\": 235385,\n\"psavert\": 9.4,\n\"uempmed\": 9.1,\n\"unemploy\": 9008 \n},\n{\n \"date\": \"1984-01-31\",\n\"pce\": 2412.8,\n\"pop\": 235527,\n\"psavert\": 10.8,\n\"uempmed\": 8.3,\n\"unemploy\": 8791 \n},\n{\n \"date\": \"1984-02-29\",\n\"pce\": 2441.3,\n\"pop\": 235675,\n\"psavert\": 10.6,\n\"uempmed\": 8.3,\n\"unemploy\": 8746 \n},\n{\n \"date\": \"1984-03-31\",\n\"pce\": 2467.6,\n\"pop\": 235839,\n\"psavert\": 10.8,\n\"uempmed\": 8.2,\n\"unemploy\": 8762 \n},\n{\n \"date\": \"1984-04-30\",\n\"pce\": 2485,\n\"pop\": 235993,\n\"psavert\": 10.5,\n\"uempmed\": 9.1,\n\"unemploy\": 8456 \n},\n{\n \"date\": \"1984-05-31\",\n\"pce\": 2506.5,\n\"pop\": 236160,\n\"psavert\": 10.6,\n\"uempmed\": 7.5,\n\"unemploy\": 8226 \n},\n{\n \"date\": \"1984-06-30\",\n\"pce\": 2505.7,\n\"pop\": 236348,\n\"psavert\": 11.4,\n\"uempmed\": 7.5,\n\"unemploy\": 8537 \n},\n{\n \"date\": \"1984-07-31\",\n\"pce\": 2523.8,\n\"pop\": 236549,\n\"psavert\": 11.3,\n\"uempmed\": 7.3,\n\"unemploy\": 8519 \n},\n{\n \"date\": \"1984-08-31\",\n\"pce\": 2545.4,\n\"pop\": 236760,\n\"psavert\": 11.2,\n\"uempmed\": 7.6,\n\"unemploy\": 8367 \n},\n{\n \"date\": \"1984-09-30\",\n\"pce\": 2543.6,\n\"pop\": 236976,\n\"psavert\": 11.4,\n\"uempmed\": 7.2,\n\"unemploy\": 8381 \n},\n{\n \"date\": \"1984-10-31\",\n\"pce\": 2584,\n\"pop\": 237159,\n\"psavert\": 10.6,\n\"uempmed\": 7.2,\n\"unemploy\": 8198 \n},\n{\n \"date\": \"1984-11-30\",\n\"pce\": 2595.3,\n\"pop\": 237316,\n\"psavert\": 11,\n\"uempmed\": 7.3,\n\"unemploy\": 8358 \n},\n{\n \"date\": \"1984-12-31\",\n\"pce\": 2629.6,\n\"pop\": 237468,\n\"psavert\": 10.3,\n\"uempmed\": 6.8,\n\"unemploy\": 8423 \n},\n{\n \"date\": \"1985-01-31\",\n\"pce\": 2650.5,\n\"pop\": 237602,\n\"psavert\": 9.1,\n\"uempmed\": 7.1,\n\"unemploy\": 8321 \n},\n{\n \"date\": \"1985-02-28\",\n\"pce\": 2657.1,\n\"pop\": 237732,\n\"psavert\": 8.7,\n\"uempmed\": 7.1,\n\"unemploy\": 8339 \n},\n{\n \"date\": \"1985-03-31\",\n\"pce\": 2668.8,\n\"pop\": 237900,\n\"psavert\": 10.1,\n\"uempmed\": 6.9,\n\"unemploy\": 8395 \n},\n{\n \"date\": \"1985-04-30\",\n\"pce\": 2705,\n\"pop\": 238074,\n\"psavert\": 11.1,\n\"uempmed\": 6.9,\n\"unemploy\": 8302 \n},\n{\n \"date\": \"1985-05-31\",\n\"pce\": 2696.4,\n\"pop\": 238270,\n\"psavert\": 9.5,\n\"uempmed\": 6.6,\n\"unemploy\": 8460 \n},\n{\n \"date\": \"1985-06-30\",\n\"pce\": 2720.5,\n\"pop\": 238466,\n\"psavert\": 8.9,\n\"uempmed\": 6.9,\n\"unemploy\": 8513 \n},\n{\n \"date\": \"1985-07-31\",\n\"pce\": 2756,\n\"pop\": 238679,\n\"psavert\": 8,\n\"uempmed\": 7.1,\n\"unemploy\": 8196 \n},\n{\n \"date\": \"1985-08-31\",\n\"pce\": 2799.7,\n\"pop\": 238898,\n\"psavert\": 6.8,\n\"uempmed\": 6.9,\n\"unemploy\": 8248 \n},\n{\n \"date\": \"1985-09-30\",\n\"pce\": 2762.3,\n\"pop\": 239113,\n\"psavert\": 8.9,\n\"uempmed\": 7.1,\n\"unemploy\": 8298 \n},\n{\n \"date\": \"1985-10-31\",\n\"pce\": 2778.7,\n\"pop\": 239307,\n\"psavert\": 8.5,\n\"uempmed\": 7,\n\"unemploy\": 8128 \n},\n{\n \"date\": \"1985-11-30\",\n\"pce\": 2819.1,\n\"pop\": 239477,\n\"psavert\": 8.3,\n\"uempmed\": 6.8,\n\"unemploy\": 8138 \n},\n{\n \"date\": \"1985-12-31\",\n\"pce\": 2833.5,\n\"pop\": 239638,\n\"psavert\": 8.2,\n\"uempmed\": 6.7,\n\"unemploy\": 7795 \n},\n{\n \"date\": \"1986-01-31\",\n\"pce\": 2826.7,\n\"pop\": 239788,\n\"psavert\": 8.9,\n\"uempmed\": 6.9,\n\"unemploy\": 8402 \n},\n{\n \"date\": \"1986-02-28\",\n\"pce\": 2830.7,\n\"pop\": 239928,\n\"psavert\": 9.5,\n\"uempmed\": 6.8,\n\"unemploy\": 8383 \n},\n{\n \"date\": \"1986-03-31\",\n\"pce\": 2843.8,\n\"pop\": 240094,\n\"psavert\": 9.1,\n\"uempmed\": 6.7,\n\"unemploy\": 8364 \n},\n{\n \"date\": \"1986-04-30\",\n\"pce\": 2867.8,\n\"pop\": 240271,\n\"psavert\": 8.7,\n\"uempmed\": 6.8,\n\"unemploy\": 8439 \n},\n{\n \"date\": \"1986-05-31\",\n\"pce\": 2874.2,\n\"pop\": 240459,\n\"psavert\": 8.9,\n\"uempmed\": 7,\n\"unemploy\": 8508 \n},\n{\n \"date\": \"1986-06-30\",\n\"pce\": 2895.9,\n\"pop\": 240651,\n\"psavert\": 8.6,\n\"uempmed\": 6.9,\n\"unemploy\": 8319 \n},\n{\n \"date\": \"1986-07-31\",\n\"pce\": 2914.8,\n\"pop\": 240854,\n\"psavert\": 8.3,\n\"uempmed\": 7.1,\n\"unemploy\": 8135 \n},\n{\n \"date\": \"1986-08-31\",\n\"pce\": 2989.8,\n\"pop\": 241068,\n\"psavert\": 6.4,\n\"uempmed\": 7.4,\n\"unemploy\": 8310 \n},\n{\n \"date\": \"1986-09-30\",\n\"pce\": 2951.6,\n\"pop\": 241274,\n\"psavert\": 7.5,\n\"uempmed\": 7,\n\"unemploy\": 8243 \n},\n{\n \"date\": \"1986-10-31\",\n\"pce\": 2948.5,\n\"pop\": 241467,\n\"psavert\": 8.1,\n\"uempmed\": 7.1,\n\"unemploy\": 8159 \n},\n{\n \"date\": \"1986-11-30\",\n\"pce\": 3019.5,\n\"pop\": 241620,\n\"psavert\": 5.9,\n\"uempmed\": 7.1,\n\"unemploy\": 7883 \n},\n{\n \"date\": \"1986-12-31\",\n\"pce\": 2959.7,\n\"pop\": 241784,\n\"psavert\": 8.8,\n\"uempmed\": 6.9,\n\"unemploy\": 7892 \n},\n{\n \"date\": \"1987-01-31\",\n\"pce\": 3026.7,\n\"pop\": 241930,\n\"psavert\": 7.6,\n\"uempmed\": 6.6,\n\"unemploy\": 7865 \n},\n{\n \"date\": \"1987-02-28\",\n\"pce\": 3037.6,\n\"pop\": 242079,\n\"psavert\": 7.7,\n\"uempmed\": 6.6,\n\"unemploy\": 7862 \n},\n{\n \"date\": \"1987-03-31\",\n\"pce\": 3061.2,\n\"pop\": 242252,\n\"psavert\": 3.5,\n\"uempmed\": 7.1,\n\"unemploy\": 7542 \n},\n{\n \"date\": \"1987-04-30\",\n\"pce\": 3070.1,\n\"pop\": 242423,\n\"psavert\": 7.2,\n\"uempmed\": 6.6,\n\"unemploy\": 7574 \n},\n{\n \"date\": \"1987-05-31\",\n\"pce\": 3094.8,\n\"pop\": 242608,\n\"psavert\": 6.7,\n\"uempmed\": 6.5,\n\"unemploy\": 7398 \n},\n{\n \"date\": \"1987-06-30\",\n\"pce\": 3118.2,\n\"pop\": 242804,\n\"psavert\": 6.5,\n\"uempmed\": 6.5,\n\"unemploy\": 7268 \n},\n{\n \"date\": \"1987-07-31\",\n\"pce\": 3155.2,\n\"pop\": 243012,\n\"psavert\": 6.2,\n\"uempmed\": 6.4,\n\"unemploy\": 7261 \n},\n{\n \"date\": \"1987-08-31\",\n\"pce\": 3151.3,\n\"pop\": 243223,\n\"psavert\": 6.7,\n\"uempmed\": 6,\n\"unemploy\": 7102 \n},\n{\n \"date\": \"1987-09-30\",\n\"pce\": 3159.6,\n\"pop\": 243446,\n\"psavert\": 7.4,\n\"uempmed\": 6.3,\n\"unemploy\": 7227 \n},\n{\n \"date\": \"1987-10-31\",\n\"pce\": 3169.3,\n\"pop\": 243639,\n\"psavert\": 7.6,\n\"uempmed\": 6.2,\n\"unemploy\": 7035 \n},\n{\n \"date\": \"1987-11-30\",\n\"pce\": 3199,\n\"pop\": 243809,\n\"psavert\": 7.7,\n\"uempmed\": 6,\n\"unemploy\": 6936 \n},\n{\n \"date\": \"1987-12-31\",\n\"pce\": 3238.6,\n\"pop\": 243981,\n\"psavert\": 7,\n\"uempmed\": 6.2,\n\"unemploy\": 6953 \n},\n{\n \"date\": \"1988-01-31\",\n\"pce\": 3246.2,\n\"pop\": 244131,\n\"psavert\": 7.5,\n\"uempmed\": 6.3,\n\"unemploy\": 6929 \n},\n{\n \"date\": \"1988-02-29\",\n\"pce\": 3285.5,\n\"pop\": 244279,\n\"psavert\": 7.2,\n\"uempmed\": 6.4,\n\"unemploy\": 6876 \n},\n{\n \"date\": \"1988-03-31\",\n\"pce\": 3288,\n\"pop\": 244445,\n\"psavert\": 7.6,\n\"uempmed\": 5.9,\n\"unemploy\": 6601 \n},\n{\n \"date\": \"1988-04-30\",\n\"pce\": 3318.5,\n\"pop\": 244610,\n\"psavert\": 7.2,\n\"uempmed\": 5.9,\n\"unemploy\": 6779 \n},\n{\n \"date\": \"1988-05-31\",\n\"pce\": 3342.7,\n\"pop\": 244806,\n\"psavert\": 7.3,\n\"uempmed\": 5.8,\n\"unemploy\": 6546 \n},\n{\n \"date\": \"1988-06-30\",\n\"pce\": 3365.6,\n\"pop\": 245021,\n\"psavert\": 7.5,\n\"uempmed\": 6.1,\n\"unemploy\": 6605 \n},\n{\n \"date\": \"1988-07-31\",\n\"pce\": 3390,\n\"pop\": 245240,\n\"psavert\": 7.2,\n\"uempmed\": 5.9,\n\"unemploy\": 6843 \n},\n{\n \"date\": \"1988-08-31\",\n\"pce\": 3396.6,\n\"pop\": 245464,\n\"psavert\": 7.5,\n\"uempmed\": 5.7,\n\"unemploy\": 6604 \n},\n{\n \"date\": \"1988-09-30\",\n\"pce\": 3436.3,\n\"pop\": 245693,\n\"psavert\": 7.2,\n\"uempmed\": 5.6,\n\"unemploy\": 6568 \n},\n{\n \"date\": \"1988-10-31\",\n\"pce\": 3452.4,\n\"pop\": 245884,\n\"psavert\": 7,\n\"uempmed\": 5.7,\n\"unemploy\": 6537 \n},\n{\n \"date\": \"1988-11-30\",\n\"pce\": 3482.8,\n\"pop\": 246056,\n\"psavert\": 7.2,\n\"uempmed\": 5.9,\n\"unemploy\": 6518 \n},\n{\n \"date\": \"1988-12-31\",\n\"pce\": 3505.3,\n\"pop\": 246224,\n\"psavert\": 7.6,\n\"uempmed\": 5.6,\n\"unemploy\": 6682 \n},\n{\n \"date\": \"1989-01-31\",\n\"pce\": 3509.3,\n\"pop\": 246378,\n\"psavert\": 7.9,\n\"uempmed\": 5.4,\n\"unemploy\": 6359 \n},\n{\n \"date\": \"1989-02-28\",\n\"pce\": 3519.3,\n\"pop\": 246530,\n\"psavert\": 8.3,\n\"uempmed\": 5.4,\n\"unemploy\": 6205 \n},\n{\n \"date\": \"1989-03-31\",\n\"pce\": 3563.2,\n\"pop\": 246721,\n\"psavert\": 7.3,\n\"uempmed\": 5.4,\n\"unemploy\": 6468 \n},\n{\n \"date\": \"1989-04-30\",\n\"pce\": 3571.8,\n\"pop\": 246906,\n\"psavert\": 7,\n\"uempmed\": 5.3,\n\"unemploy\": 6375 \n},\n{\n \"date\": \"1989-05-31\",\n\"pce\": 3586.7,\n\"pop\": 247114,\n\"psavert\": 7.1,\n\"uempmed\": 5.4,\n\"unemploy\": 6577 \n},\n{\n \"date\": \"1989-06-30\",\n\"pce\": 3606.4,\n\"pop\": 247342,\n\"psavert\": 7.1,\n\"uempmed\": 5.6,\n\"unemploy\": 6495 \n},\n{\n \"date\": \"1989-07-31\",\n\"pce\": 3642.2,\n\"pop\": 247573,\n\"psavert\": 6.4,\n\"uempmed\": 5,\n\"unemploy\": 6511 \n},\n{\n \"date\": \"1989-08-31\",\n\"pce\": 3644.2,\n\"pop\": 247816,\n\"psavert\": 6.6,\n\"uempmed\": 4.9,\n\"unemploy\": 6590 \n},\n{\n \"date\": \"1989-09-30\",\n\"pce\": 3657,\n\"pop\": 248067,\n\"psavert\": 6.8,\n\"uempmed\": 4.9,\n\"unemploy\": 6630 \n},\n{\n \"date\": \"1989-10-31\",\n\"pce\": 3667.6,\n\"pop\": 248281,\n\"psavert\": 7.2,\n\"uempmed\": 4.8,\n\"unemploy\": 6725 \n},\n{\n \"date\": \"1989-11-30\",\n\"pce\": 3708.9,\n\"pop\": 248479,\n\"psavert\": 6.5,\n\"uempmed\": 4.9,\n\"unemploy\": 6667 \n},\n{\n \"date\": \"1989-12-31\",\n\"pce\": 3754.5,\n\"pop\": 248659,\n\"psavert\": 6.6,\n\"uempmed\": 5.1,\n\"unemploy\": 6752 \n},\n{\n \"date\": \"1990-01-31\",\n\"pce\": 3752.2,\n\"pop\": 248827,\n\"psavert\": 7.3,\n\"uempmed\": 5.3,\n\"unemploy\": 6651 \n},\n{\n \"date\": \"1990-02-28\",\n\"pce\": 3781,\n\"pop\": 249012,\n\"psavert\": 7,\n\"uempmed\": 5.1,\n\"unemploy\": 6598 \n},\n{\n \"date\": \"1990-03-31\",\n\"pce\": 3800.5,\n\"pop\": 249306,\n\"psavert\": 7.3,\n\"uempmed\": 4.8,\n\"unemploy\": 6797 \n},\n{\n \"date\": \"1990-04-30\",\n\"pce\": 3808.6,\n\"pop\": 249565,\n\"psavert\": 7.2,\n\"uempmed\": 5.2,\n\"unemploy\": 6742 \n},\n{\n \"date\": \"1990-05-31\",\n\"pce\": 3838.5,\n\"pop\": 249849,\n\"psavert\": 7.1,\n\"uempmed\": 5.2,\n\"unemploy\": 6590 \n},\n{\n \"date\": \"1990-06-30\",\n\"pce\": 3855.1,\n\"pop\": 250132,\n\"psavert\": 7.2,\n\"uempmed\": 5.4,\n\"unemploy\": 6922 \n},\n{\n \"date\": \"1990-07-31\",\n\"pce\": 3881,\n\"pop\": 250439,\n\"psavert\": 6.7,\n\"uempmed\": 5.4,\n\"unemploy\": 7188 \n},\n{\n \"date\": \"1990-08-31\",\n\"pce\": 3902.7,\n\"pop\": 250751,\n\"psavert\": 6.7,\n\"uempmed\": 5.6,\n\"unemploy\": 7368 \n},\n{\n \"date\": \"1990-09-30\",\n\"pce\": 3902.9,\n\"pop\": 251057,\n\"psavert\": 6.6,\n\"uempmed\": 5.8,\n\"unemploy\": 7459 \n},\n{\n \"date\": \"1990-10-31\",\n\"pce\": 3905.6,\n\"pop\": 251346,\n\"psavert\": 6.7,\n\"uempmed\": 5.7,\n\"unemploy\": 7764 \n},\n{\n \"date\": \"1990-11-30\",\n\"pce\": 3896.6,\n\"pop\": 251626,\n\"psavert\": 7.3,\n\"uempmed\": 5.9,\n\"unemploy\": 7901 \n},\n{\n \"date\": \"1990-12-31\",\n\"pce\": 3879.3,\n\"pop\": 251889,\n\"psavert\": 7.9,\n\"uempmed\": 6,\n\"unemploy\": 8015 \n},\n{\n \"date\": \"1991-01-31\",\n\"pce\": 3907.7,\n\"pop\": 252135,\n\"psavert\": 7.5,\n\"uempmed\": 6.2,\n\"unemploy\": 8265 \n},\n{\n \"date\": \"1991-02-28\",\n\"pce\": 3955.6,\n\"pop\": 252372,\n\"psavert\": 6.6,\n\"uempmed\": 6.7,\n\"unemploy\": 8586 \n},\n{\n \"date\": \"1991-03-31\",\n\"pce\": 3950.5,\n\"pop\": 252643,\n\"psavert\": 7.1,\n\"uempmed\": 6.6,\n\"unemploy\": 8439 \n},\n{\n \"date\": \"1991-04-30\",\n\"pce\": 3976.8,\n\"pop\": 252913,\n\"psavert\": 6.9,\n\"uempmed\": 6.4,\n\"unemploy\": 8736 \n},\n{\n \"date\": \"1991-05-31\",\n\"pce\": 3983.6,\n\"pop\": 253207,\n\"psavert\": 7.4,\n\"uempmed\": 6.9,\n\"unemploy\": 8692 \n},\n{\n \"date\": \"1991-06-30\",\n\"pce\": 4008.4,\n\"pop\": 253493,\n\"psavert\": 6.8,\n\"uempmed\": 7,\n\"unemploy\": 8586 \n},\n{\n \"date\": \"1991-07-31\",\n\"pce\": 4011.3,\n\"pop\": 253807,\n\"psavert\": 7,\n\"uempmed\": 7.3,\n\"unemploy\": 8666 \n},\n{\n \"date\": \"1991-08-31\",\n\"pce\": 4027.3,\n\"pop\": 254126,\n\"psavert\": 7.2,\n\"uempmed\": 6.8,\n\"unemploy\": 8722 \n},\n{\n \"date\": \"1991-09-30\",\n\"pce\": 4020.1,\n\"pop\": 254435,\n\"psavert\": 7.5,\n\"uempmed\": 7.2,\n\"unemploy\": 8842 \n},\n{\n \"date\": \"1991-10-31\",\n\"pce\": 4048.2,\n\"pop\": 254718,\n\"psavert\": 7.3,\n\"uempmed\": 7.5,\n\"unemploy\": 8931 \n},\n{\n \"date\": \"1991-11-30\",\n\"pce\": 4064,\n\"pop\": 254964,\n\"psavert\": 7.9,\n\"uempmed\": 7.8,\n\"unemploy\": 9198 \n},\n{\n \"date\": \"1991-12-31\",\n\"pce\": 4128.2,\n\"pop\": 255214,\n\"psavert\": 7.4,\n\"uempmed\": 8.1,\n\"unemploy\": 9283 \n},\n{\n \"date\": \"1992-01-31\",\n\"pce\": 4141.8,\n\"pop\": 255448,\n\"psavert\": 7.9,\n\"uempmed\": 8.2,\n\"unemploy\": 9454 \n},\n{\n \"date\": \"1992-02-29\",\n\"pce\": 4157.6,\n\"pop\": 255703,\n\"psavert\": 7.9,\n\"uempmed\": 8.3,\n\"unemploy\": 9460 \n},\n{\n \"date\": \"1992-03-31\",\n\"pce\": 4169.8,\n\"pop\": 255992,\n\"psavert\": 8,\n\"uempmed\": 8.5,\n\"unemploy\": 9415 \n},\n{\n \"date\": \"1992-04-30\",\n\"pce\": 4195.5,\n\"pop\": 256285,\n\"psavert\": 7.9,\n\"uempmed\": 8.8,\n\"unemploy\": 9744 \n},\n{\n \"date\": \"1992-05-31\",\n\"pce\": 4213.8,\n\"pop\": 256589,\n\"psavert\": 7.8,\n\"uempmed\": 8.7,\n\"unemploy\": 10040 \n},\n{\n \"date\": \"1992-06-30\",\n\"pce\": 4241.8,\n\"pop\": 256894,\n\"psavert\": 7.5,\n\"uempmed\": 8.6,\n\"unemploy\": 9850 \n},\n{\n \"date\": \"1992-07-31\",\n\"pce\": 4258.8,\n\"pop\": 257232,\n\"psavert\": 7.6,\n\"uempmed\": 8.8,\n\"unemploy\": 9787 \n},\n{\n \"date\": \"1992-08-31\",\n\"pce\": 4292.5,\n\"pop\": 257548,\n\"psavert\": 6.9,\n\"uempmed\": 8.6,\n\"unemploy\": 9781 \n},\n{\n \"date\": \"1992-09-30\",\n\"pce\": 4320.2,\n\"pop\": 257861,\n\"psavert\": 7.1,\n\"uempmed\": 9,\n\"unemploy\": 9398 \n},\n{\n \"date\": \"1992-10-31\",\n\"pce\": 4334.3,\n\"pop\": 258147,\n\"psavert\": 7,\n\"uempmed\": 9,\n\"unemploy\": 9565 \n},\n{\n \"date\": \"1992-11-30\",\n\"pce\": 4368.8,\n\"pop\": 258413,\n\"psavert\": 9.4,\n\"uempmed\": 9.3,\n\"unemploy\": 9557 \n},\n{\n \"date\": \"1992-12-31\",\n\"pce\": 4371.5,\n\"pop\": 258679,\n\"psavert\": 5.8,\n\"uempmed\": 8.6,\n\"unemploy\": 9325 \n},\n{\n \"date\": \"1993-01-31\",\n\"pce\": 4385,\n\"pop\": 258919,\n\"psavert\": 5.6,\n\"uempmed\": 8.5,\n\"unemploy\": 9183 \n},\n{\n \"date\": \"1993-02-28\",\n\"pce\": 4381.5,\n\"pop\": 259152,\n\"psavert\": 5.6,\n\"uempmed\": 8.5,\n\"unemploy\": 9056 \n},\n{\n \"date\": \"1993-03-31\",\n\"pce\": 4422.5,\n\"pop\": 259414,\n\"psavert\": 6.4,\n\"uempmed\": 8.4,\n\"unemploy\": 9110 \n},\n{\n \"date\": \"1993-04-30\",\n\"pce\": 4450.9,\n\"pop\": 259680,\n\"psavert\": 6.3,\n\"uempmed\": 8.1,\n\"unemploy\": 9149 \n},\n{\n \"date\": \"1993-05-31\",\n\"pce\": 4466.7,\n\"pop\": 259963,\n\"psavert\": 5.9,\n\"uempmed\": 8.3,\n\"unemploy\": 9121 \n},\n{\n \"date\": \"1993-06-30\",\n\"pce\": 4493.8,\n\"pop\": 260255,\n\"psavert\": 5.4,\n\"uempmed\": 8.2,\n\"unemploy\": 8930 \n},\n{\n \"date\": \"1993-07-31\",\n\"pce\": 4504.3,\n\"pop\": 260566,\n\"psavert\": 5.6,\n\"uempmed\": 8.2,\n\"unemploy\": 8763 \n},\n{\n \"date\": \"1993-08-31\",\n\"pce\": 4534,\n\"pop\": 260867,\n\"psavert\": 5,\n\"uempmed\": 8.3,\n\"unemploy\": 8714 \n},\n{\n \"date\": \"1993-09-30\",\n\"pce\": 4554.8,\n\"pop\": 261163,\n\"psavert\": 5,\n\"uempmed\": 8,\n\"unemploy\": 8750 \n},\n{\n \"date\": \"1993-10-31\",\n\"pce\": 4575.9,\n\"pop\": 261425,\n\"psavert\": 5,\n\"uempmed\": 8.3,\n\"unemploy\": 8542 \n},\n{\n \"date\": \"1993-11-30\",\n\"pce\": 4593.9,\n\"pop\": 261674,\n\"psavert\": 7.6,\n\"uempmed\": 8.3,\n\"unemploy\": 8477 \n},\n{\n \"date\": \"1993-12-31\",\n\"pce\": 4608.5,\n\"pop\": 261919,\n\"psavert\": 4,\n\"uempmed\": 8.6,\n\"unemploy\": 8630 \n},\n{\n \"date\": \"1994-01-31\",\n\"pce\": 4655.7,\n\"pop\": 262123,\n\"psavert\": 3.9,\n\"uempmed\": 9.2,\n\"unemploy\": 8583 \n},\n{\n \"date\": \"1994-02-28\",\n\"pce\": 4667.5,\n\"pop\": 262352,\n\"psavert\": 4.3,\n\"uempmed\": 9.3,\n\"unemploy\": 8470 \n},\n{\n \"date\": \"1994-03-31\",\n\"pce\": 4690.3,\n\"pop\": 262631,\n\"psavert\": 4.2,\n\"uempmed\": 9.1,\n\"unemploy\": 8331 \n},\n{\n \"date\": \"1994-04-30\",\n\"pce\": 4688.3,\n\"pop\": 262877,\n\"psavert\": 5.8,\n\"uempmed\": 9.2,\n\"unemploy\": 7915 \n},\n{\n \"date\": \"1994-05-31\",\n\"pce\": 4729.9,\n\"pop\": 263152,\n\"psavert\": 5.1,\n\"uempmed\": 9.3,\n\"unemploy\": 7927 \n},\n{\n \"date\": \"1994-06-30\",\n\"pce\": 4745.4,\n\"pop\": 263436,\n\"psavert\": 5.1,\n\"uempmed\": 9,\n\"unemploy\": 7946 \n},\n{\n \"date\": \"1994-07-31\",\n\"pce\": 4789.2,\n\"pop\": 263724,\n\"psavert\": 4.7,\n\"uempmed\": 8.9,\n\"unemploy\": 7933 \n},\n{\n \"date\": \"1994-08-31\",\n\"pce\": 4801.2,\n\"pop\": 264017,\n\"psavert\": 5,\n\"uempmed\": 9.2,\n\"unemploy\": 7734 \n},\n{\n \"date\": \"1994-09-30\",\n\"pce\": 4836.2,\n\"pop\": 264301,\n\"psavert\": 5.3,\n\"uempmed\": 10,\n\"unemploy\": 7632 \n},\n{\n \"date\": \"1994-10-31\",\n\"pce\": 4846.5,\n\"pop\": 264559,\n\"psavert\": 5.2,\n\"uempmed\": 9,\n\"unemploy\": 7375 \n},\n{\n \"date\": \"1994-11-30\",\n\"pce\": 4860.9,\n\"pop\": 264804,\n\"psavert\": 5.3,\n\"uempmed\": 8.7,\n\"unemploy\": 7230 \n},\n{\n \"date\": \"1994-12-31\",\n\"pce\": 4869.3,\n\"pop\": 265044,\n\"psavert\": 5.6,\n\"uempmed\": 8,\n\"unemploy\": 7375 \n},\n{\n \"date\": \"1995-01-31\",\n\"pce\": 4867.4,\n\"pop\": 265270,\n\"psavert\": 5.9,\n\"uempmed\": 8.1,\n\"unemploy\": 7187 \n},\n{\n \"date\": \"1995-02-28\",\n\"pce\": 4900.5,\n\"pop\": 265495,\n\"psavert\": 5.5,\n\"uempmed\": 8.3,\n\"unemploy\": 7153 \n},\n{\n \"date\": \"1995-03-31\",\n\"pce\": 4904.2,\n\"pop\": 265755,\n\"psavert\": 4.8,\n\"uempmed\": 8.3,\n\"unemploy\": 7645 \n},\n{\n \"date\": \"1995-04-30\",\n\"pce\": 4946.1,\n\"pop\": 265998,\n\"psavert\": 4.9,\n\"uempmed\": 9.1,\n\"unemploy\": 7430 \n},\n{\n \"date\": \"1995-05-31\",\n\"pce\": 4989.8,\n\"pop\": 266270,\n\"psavert\": 4.4,\n\"uempmed\": 7.9,\n\"unemploy\": 7427 \n},\n{\n \"date\": \"1995-06-30\",\n\"pce\": 4982.7,\n\"pop\": 266557,\n\"psavert\": 4.6,\n\"uempmed\": 8.5,\n\"unemploy\": 7527 \n},\n{\n \"date\": \"1995-07-31\",\n\"pce\": 5018,\n\"pop\": 266843,\n\"psavert\": 4.1,\n\"uempmed\": 8.3,\n\"unemploy\": 7484 \n},\n{\n \"date\": \"1995-08-31\",\n\"pce\": 5032.5,\n\"pop\": 267152,\n\"psavert\": 4.1,\n\"uempmed\": 7.9,\n\"unemploy\": 7478 \n},\n{\n \"date\": \"1995-09-30\",\n\"pce\": 5024.5,\n\"pop\": 267456,\n\"psavert\": 4.4,\n\"uempmed\": 8.2,\n\"unemploy\": 7328 \n},\n{\n \"date\": \"1995-10-31\",\n\"pce\": 5065.8,\n\"pop\": 267715,\n\"psavert\": 3.9,\n\"uempmed\": 8,\n\"unemploy\": 7426 \n},\n{\n \"date\": \"1995-11-30\",\n\"pce\": 5108.8,\n\"pop\": 267943,\n\"psavert\": 3.6,\n\"uempmed\": 8.3,\n\"unemploy\": 7423 \n},\n{\n \"date\": \"1995-12-31\",\n\"pce\": 5098,\n\"pop\": 268151,\n\"psavert\": 4.2,\n\"uempmed\": 8.3,\n\"unemploy\": 7491 \n},\n{\n \"date\": \"1996-01-31\",\n\"pce\": 5145.2,\n\"pop\": 268364,\n\"psavert\": 4.3,\n\"uempmed\": 7.8,\n\"unemploy\": 7313 \n},\n{\n \"date\": \"1996-02-29\",\n\"pce\": 5185.1,\n\"pop\": 268595,\n\"psavert\": 4.2,\n\"uempmed\": 8.3,\n\"unemploy\": 7318 \n},\n{\n \"date\": \"1996-03-31\",\n\"pce\": 5219.6,\n\"pop\": 268853,\n\"psavert\": 3.1,\n\"uempmed\": 8.6,\n\"unemploy\": 7415 \n},\n{\n \"date\": \"1996-04-30\",\n\"pce\": 5234.8,\n\"pop\": 269108,\n\"psavert\": 4.1,\n\"uempmed\": 8.6,\n\"unemploy\": 7423 \n},\n{\n \"date\": \"1996-05-31\",\n\"pce\": 5241.6,\n\"pop\": 269386,\n\"psavert\": 4.5,\n\"uempmed\": 8.3,\n\"unemploy\": 7095 \n},\n{\n \"date\": \"1996-06-30\",\n\"pce\": 5263.6,\n\"pop\": 269667,\n\"psavert\": 4.1,\n\"uempmed\": 8.3,\n\"unemploy\": 7337 \n},\n{\n \"date\": \"1996-07-31\",\n\"pce\": 5287.5,\n\"pop\": 269976,\n\"psavert\": 4.1,\n\"uempmed\": 8.4,\n\"unemploy\": 6882 \n},\n{\n \"date\": \"1996-08-31\",\n\"pce\": 5308.2,\n\"pop\": 270284,\n\"psavert\": 4.1,\n\"uempmed\": 8.5,\n\"unemploy\": 6979 \n},\n{\n \"date\": \"1996-09-30\",\n\"pce\": 5340.1,\n\"pop\": 270581,\n\"psavert\": 3.8,\n\"uempmed\": 8.3,\n\"unemploy\": 7031 \n},\n{\n \"date\": \"1996-10-31\",\n\"pce\": 5365.5,\n\"pop\": 270878,\n\"psavert\": 3.8,\n\"uempmed\": 7.7,\n\"unemploy\": 7236 \n},\n{\n \"date\": \"1996-11-30\",\n\"pce\": 5392.7,\n\"pop\": 271125,\n\"psavert\": 3.8,\n\"uempmed\": 7.8,\n\"unemploy\": 7253 \n},\n{\n \"date\": \"1996-12-31\",\n\"pce\": 5419.9,\n\"pop\": 271360,\n\"psavert\": 3.7,\n\"uempmed\": 7.8,\n\"unemploy\": 7158 \n},\n{\n \"date\": \"1997-01-31\",\n\"pce\": 5453.9,\n\"pop\": 271585,\n\"psavert\": 3.5,\n\"uempmed\": 8.1,\n\"unemploy\": 7102 \n},\n{\n \"date\": \"1997-02-28\",\n\"pce\": 5472.6,\n\"pop\": 271821,\n\"psavert\": 3.7,\n\"uempmed\": 7.9,\n\"unemploy\": 7000 \n},\n{\n \"date\": \"1997-03-31\",\n\"pce\": 5473.4,\n\"pop\": 272083,\n\"psavert\": 3.8,\n\"uempmed\": 8.3,\n\"unemploy\": 6873 \n},\n{\n \"date\": \"1997-04-30\",\n\"pce\": 5474.4,\n\"pop\": 272342,\n\"psavert\": 4,\n\"uempmed\": 8,\n\"unemploy\": 6655 \n},\n{\n \"date\": \"1997-05-31\",\n\"pce\": 5506.1,\n\"pop\": 272622,\n\"psavert\": 3.9,\n\"uempmed\": 8,\n\"unemploy\": 6799 \n},\n{\n \"date\": \"1997-06-30\",\n\"pce\": 5565,\n\"pop\": 272912,\n\"psavert\": 3.3,\n\"uempmed\": 8.3,\n\"unemploy\": 6655 \n},\n{\n \"date\": \"1997-07-31\",\n\"pce\": 5596.7,\n\"pop\": 273237,\n\"psavert\": 3.3,\n\"uempmed\": 7.8,\n\"unemploy\": 6608 \n},\n{\n \"date\": \"1997-08-31\",\n\"pce\": 5607.6,\n\"pop\": 273553,\n\"psavert\": 3.6,\n\"uempmed\": 8.2,\n\"unemploy\": 6656 \n},\n{\n \"date\": \"1997-09-30\",\n\"pce\": 5639.2,\n\"pop\": 273852,\n\"psavert\": 3.5,\n\"uempmed\": 7.7,\n\"unemploy\": 6454 \n},\n{\n \"date\": \"1997-10-31\",\n\"pce\": 5666.1,\n\"pop\": 274126,\n\"psavert\": 3.7,\n\"uempmed\": 7.6,\n\"unemploy\": 6308 \n},\n{\n \"date\": \"1997-11-30\",\n\"pce\": 5694,\n\"pop\": 274372,\n\"psavert\": 3.8,\n\"uempmed\": 7.5,\n\"unemploy\": 6476 \n},\n{\n \"date\": \"1997-12-31\",\n\"pce\": 5698.7,\n\"pop\": 274626,\n\"psavert\": 4.6,\n\"uempmed\": 7.4,\n\"unemploy\": 6368 \n},\n{\n \"date\": \"1998-01-31\",\n\"pce\": 5736.6,\n\"pop\": 274838,\n\"psavert\": 4.6,\n\"uempmed\": 7,\n\"unemploy\": 6306 \n},\n{\n \"date\": \"1998-02-28\",\n\"pce\": 5764.8,\n\"pop\": 275047,\n\"psavert\": 4.7,\n\"uempmed\": 6.8,\n\"unemploy\": 6422 \n},\n{\n \"date\": \"1998-03-31\",\n\"pce\": 5788.9,\n\"pop\": 275304,\n\"psavert\": 4.7,\n\"uempmed\": 6.7,\n\"unemploy\": 5941 \n},\n{\n \"date\": \"1998-04-30\",\n\"pce\": 5842.9,\n\"pop\": 275564,\n\"psavert\": 4.4,\n\"uempmed\": 6,\n\"unemploy\": 6047 \n},\n{\n \"date\": \"1998-05-31\",\n\"pce\": 5870.8,\n\"pop\": 275836,\n\"psavert\": 4.4,\n\"uempmed\": 6.9,\n\"unemploy\": 6212 \n},\n{\n \"date\": \"1998-06-30\",\n\"pce\": 5887.4,\n\"pop\": 276115,\n\"psavert\": 4.5,\n\"uempmed\": 6.7,\n\"unemploy\": 6259 \n},\n{\n \"date\": \"1998-07-31\",\n\"pce\": 5928.8,\n\"pop\": 276418,\n\"psavert\": 4.3,\n\"uempmed\": 6.8,\n\"unemploy\": 6179 \n},\n{\n \"date\": \"1998-08-31\",\n\"pce\": 5956.3,\n\"pop\": 276714,\n\"psavert\": 4.2,\n\"uempmed\": 6.7,\n\"unemploy\": 6300 \n},\n{\n \"date\": \"1998-09-30\",\n\"pce\": 5995.2,\n\"pop\": 277003,\n\"psavert\": 3.9,\n\"uempmed\": 5.8,\n\"unemploy\": 6280 \n},\n{\n \"date\": \"1998-10-31\",\n\"pce\": 6018.5,\n\"pop\": 277277,\n\"psavert\": 4,\n\"uempmed\": 6.6,\n\"unemploy\": 6100 \n},\n{\n \"date\": \"1998-11-30\",\n\"pce\": 6064.8,\n\"pop\": 277526,\n\"psavert\": 3.5,\n\"uempmed\": 6.8,\n\"unemploy\": 6032 \n},\n{\n \"date\": \"1998-12-31\",\n\"pce\": 6067.4,\n\"pop\": 277790,\n\"psavert\": 4,\n\"uempmed\": 6.9,\n\"unemploy\": 5976 \n},\n{\n \"date\": \"1999-01-31\",\n\"pce\": 6099.7,\n\"pop\": 277992,\n\"psavert\": 3.7,\n\"uempmed\": 6.8,\n\"unemploy\": 6111 \n},\n{\n \"date\": \"1999-02-28\",\n\"pce\": 6138,\n\"pop\": 278198,\n\"psavert\": 3.3,\n\"uempmed\": 6.8,\n\"unemploy\": 5783 \n},\n{\n \"date\": \"1999-03-31\",\n\"pce\": 6202.5,\n\"pop\": 278451,\n\"psavert\": 2.5,\n\"uempmed\": 6.2,\n\"unemploy\": 6004 \n},\n{\n \"date\": \"1999-04-30\",\n\"pce\": 6245.1,\n\"pop\": 278717,\n\"psavert\": 2.1,\n\"uempmed\": 6.5,\n\"unemploy\": 5796 \n},\n{\n \"date\": \"1999-05-31\",\n\"pce\": 6264.1,\n\"pop\": 279001,\n\"psavert\": 2.1,\n\"uempmed\": 6.3,\n\"unemploy\": 5951 \n},\n{\n \"date\": \"1999-06-30\",\n\"pce\": 6297.3,\n\"pop\": 279295,\n\"psavert\": 1.9,\n\"uempmed\": 5.8,\n\"unemploy\": 6025 \n},\n{\n \"date\": \"1999-07-31\",\n\"pce\": 6338.6,\n\"pop\": 279602,\n\"psavert\": 1.8,\n\"uempmed\": 6.5,\n\"unemploy\": 5838 \n},\n{\n \"date\": \"1999-08-31\",\n\"pce\": 6375.7,\n\"pop\": 279903,\n\"psavert\": 1.4,\n\"uempmed\": 6,\n\"unemploy\": 5915 \n},\n{\n \"date\": \"1999-09-30\",\n\"pce\": 6396.7,\n\"pop\": 280203,\n\"psavert\": 2,\n\"uempmed\": 6.1,\n\"unemploy\": 5778 \n},\n{\n \"date\": \"1999-10-31\",\n\"pce\": 6433.2,\n\"pop\": 280471,\n\"psavert\": 2.1,\n\"uempmed\": 6.2,\n\"unemploy\": 5716 \n},\n{\n \"date\": \"1999-11-30\",\n\"pce\": 6531.3,\n\"pop\": 280716,\n\"psavert\": 1.6,\n\"uempmed\": 5.8,\n\"unemploy\": 5653 \n},\n{\n \"date\": \"1999-12-31\",\n\"pce\": 6538,\n\"pop\": 280976,\n\"psavert\": 2.9,\n\"uempmed\": 5.8,\n\"unemploy\": 5708 \n},\n{\n \"date\": \"2000-01-31\",\n\"pce\": 6618.5,\n\"pop\": 281190,\n\"psavert\": 2.4,\n\"uempmed\": 6.1,\n\"unemploy\": 5858 \n},\n{\n \"date\": \"2000-02-29\",\n\"pce\": 6685.3,\n\"pop\": 281409,\n\"psavert\": 2,\n\"uempmed\": 6,\n\"unemploy\": 5733 \n},\n{\n \"date\": \"2000-03-31\",\n\"pce\": 6664.2,\n\"pop\": 281653,\n\"psavert\": 2.4,\n\"uempmed\": 6.1,\n\"unemploy\": 5481 \n},\n{\n \"date\": \"2000-04-30\",\n\"pce\": 6688,\n\"pop\": 281891,\n\"psavert\": 2.4,\n\"uempmed\": 5.8,\n\"unemploy\": 5758 \n},\n{\n \"date\": \"2000-05-31\",\n\"pce\": 6712.1,\n\"pop\": 282156,\n\"psavert\": 2.5,\n\"uempmed\": 5.7,\n\"unemploy\": 5651 \n},\n{\n \"date\": \"2000-06-30\",\n\"pce\": 6745.8,\n\"pop\": 282430,\n\"psavert\": 2.9,\n\"uempmed\": 6,\n\"unemploy\": 5747 \n},\n{\n \"date\": \"2000-07-31\",\n\"pce\": 6766.7,\n\"pop\": 282706,\n\"psavert\": 2.8,\n\"uempmed\": 6.3,\n\"unemploy\": 5853 \n},\n{\n \"date\": \"2000-08-31\",\n\"pce\": 6839.3,\n\"pop\": 282994,\n\"psavert\": 2.2,\n\"uempmed\": 5.2,\n\"unemploy\": 5625 \n},\n{\n \"date\": \"2000-09-30\",\n\"pce\": 6846.2,\n\"pop\": 283271,\n\"psavert\": 2.3,\n\"uempmed\": 6.1,\n\"unemploy\": 5534 \n},\n{\n \"date\": \"2000-10-31\",\n\"pce\": 6860.2,\n\"pop\": 283531,\n\"psavert\": 2.1,\n\"uempmed\": 6.1,\n\"unemploy\": 5639 \n},\n{\n \"date\": \"2000-11-30\",\n\"pce\": 6908.5,\n\"pop\": 283782,\n\"psavert\": 1.5,\n\"uempmed\": 6,\n\"unemploy\": 5634 \n},\n{\n \"date\": \"2000-12-31\",\n\"pce\": 6938.2,\n\"pop\": 284015,\n\"psavert\": 1.9,\n\"uempmed\": 5.8,\n\"unemploy\": 6023 \n},\n{\n \"date\": \"2001-01-31\",\n\"pce\": 6969.2,\n\"pop\": 284240,\n\"psavert\": 1.7,\n\"uempmed\": 6.1,\n\"unemploy\": 6089 \n},\n{\n \"date\": \"2001-02-28\",\n\"pce\": 6960.1,\n\"pop\": 284462,\n\"psavert\": 2,\n\"uempmed\": 6.6,\n\"unemploy\": 6141 \n},\n{\n \"date\": \"2001-03-31\",\n\"pce\": 6978.5,\n\"pop\": 284701,\n\"psavert\": 1.6,\n\"uempmed\": 5.9,\n\"unemploy\": 6271 \n},\n{\n \"date\": \"2001-04-30\",\n\"pce\": 7029.1,\n\"pop\": 284938,\n\"psavert\": 1,\n\"uempmed\": 6.3,\n\"unemploy\": 6226 \n},\n{\n \"date\": \"2001-05-31\",\n\"pce\": 7045,\n\"pop\": 285198,\n\"psavert\": 1.1,\n\"uempmed\": 6,\n\"unemploy\": 6484 \n},\n{\n \"date\": \"2001-06-30\",\n\"pce\": 7064.1,\n\"pop\": 285454,\n\"psavert\": 2.4,\n\"uempmed\": 6.8,\n\"unemploy\": 6583 \n},\n{\n \"date\": \"2001-07-31\",\n\"pce\": 7098.6,\n\"pop\": 285730,\n\"psavert\": 3.7,\n\"uempmed\": 6.9,\n\"unemploy\": 7042 \n},\n{\n \"date\": \"2001-08-31\",\n\"pce\": 7012.7,\n\"pop\": 286017,\n\"psavert\": 4.2,\n\"uempmed\": 7.2,\n\"unemploy\": 7142 \n},\n{\n \"date\": \"2001-09-30\",\n\"pce\": 7222,\n\"pop\": 286287,\n\"psavert\": -0.2,\n\"uempmed\": 7.3,\n\"unemploy\": 7694 \n},\n{\n \"date\": \"2001-10-31\",\n\"pce\": 7177.2,\n\"pop\": 286545,\n\"psavert\": 0.7,\n\"uempmed\": 7.7,\n\"unemploy\": 8003 \n},\n{\n \"date\": \"2001-11-30\",\n\"pce\": 7165.9,\n\"pop\": 286788,\n\"psavert\": 1.1,\n\"uempmed\": 8.2,\n\"unemploy\": 8258 \n},\n{\n \"date\": \"2001-12-31\",\n\"pce\": 7196.5,\n\"pop\": 287021,\n\"psavert\": 2.9,\n\"uempmed\": 8.4,\n\"unemploy\": 8182 \n},\n{\n \"date\": \"2002-01-31\",\n\"pce\": 7242,\n\"pop\": 287242,\n\"psavert\": 2.8,\n\"uempmed\": 8.3,\n\"unemploy\": 8215 \n},\n{\n \"date\": \"2002-02-28\",\n\"pce\": 7252.3,\n\"pop\": 287453,\n\"psavert\": 3,\n\"uempmed\": 8.4,\n\"unemploy\": 8304 \n},\n{\n \"date\": \"2002-03-31\",\n\"pce\": 7330.2,\n\"pop\": 287675,\n\"psavert\": 2.6,\n\"uempmed\": 8.9,\n\"unemploy\": 8599 \n},\n{\n \"date\": \"2002-04-30\",\n\"pce\": 7296.2,\n\"pop\": 287916,\n\"psavert\": 3.1,\n\"uempmed\": 9.5,\n\"unemploy\": 8399 \n},\n{\n \"date\": \"2002-05-31\",\n\"pce\": 7342.6,\n\"pop\": 288171,\n\"psavert\": 2.8,\n\"uempmed\": 11,\n\"unemploy\": 8393 \n},\n{\n \"date\": \"2002-06-30\",\n\"pce\": 7396.4,\n\"pop\": 288427,\n\"psavert\": 1.9,\n\"uempmed\": 8.9,\n\"unemploy\": 8390 \n},\n{\n \"date\": \"2002-07-31\",\n\"pce\": 7411,\n\"pop\": 288694,\n\"psavert\": 1.7,\n\"uempmed\": 9,\n\"unemploy\": 8304 \n},\n{\n \"date\": \"2002-08-31\",\n\"pce\": 7382.3,\n\"pop\": 288965,\n\"psavert\": 2.2,\n\"uempmed\": 9.5,\n\"unemploy\": 8251 \n},\n{\n \"date\": \"2002-09-30\",\n\"pce\": 7414.3,\n\"pop\": 289229,\n\"psavert\": 2,\n\"uempmed\": 9.6,\n\"unemploy\": 8307 \n},\n{\n \"date\": \"2002-10-31\",\n\"pce\": 7443.6,\n\"pop\": 289477,\n\"psavert\": 1.8,\n\"uempmed\": 9.3,\n\"unemploy\": 8520 \n},\n{\n \"date\": \"2002-11-30\",\n\"pce\": 7501.3,\n\"pop\": 289696,\n\"psavert\": 1.5,\n\"uempmed\": 9.6,\n\"unemploy\": 8640 \n},\n{\n \"date\": \"2002-12-31\",\n\"pce\": 7522.1,\n\"pop\": 289913,\n\"psavert\": 1.8,\n\"uempmed\": 9.6,\n\"unemploy\": 8523 \n},\n{\n \"date\": \"2003-01-31\",\n\"pce\": 7532.8,\n\"pop\": 290122,\n\"psavert\": 2,\n\"uempmed\": 9.5,\n\"unemploy\": 8622 \n},\n{\n \"date\": \"2003-02-28\",\n\"pce\": 7589.5,\n\"pop\": 290331,\n\"psavert\": 1.7,\n\"uempmed\": 9.7,\n\"unemploy\": 8576 \n},\n{\n \"date\": \"2003-03-31\",\n\"pce\": 7597.2,\n\"pop\": 290557,\n\"psavert\": 2,\n\"uempmed\": 10.2,\n\"unemploy\": 8833 \n},\n{\n \"date\": \"2003-04-30\",\n\"pce\": 7619.2,\n\"pop\": 290791,\n\"psavert\": 2.3,\n\"uempmed\": 9.9,\n\"unemploy\": 8948 \n},\n{\n \"date\": \"2003-05-31\",\n\"pce\": 7668.8,\n\"pop\": 291041,\n\"psavert\": 2.1,\n\"uempmed\": 11.5,\n\"unemploy\": 9254 \n},\n{\n \"date\": \"2003-06-30\",\n\"pce\": 7723.3,\n\"pop\": 291289,\n\"psavert\": 2.8,\n\"uempmed\": 10.3,\n\"unemploy\": 9018 \n},\n{\n \"date\": \"2003-07-31\",\n\"pce\": 7820.9,\n\"pop\": 291552,\n\"psavert\": 2.5,\n\"uempmed\": 10.1,\n\"unemploy\": 8894 \n},\n{\n \"date\": \"2003-08-31\",\n\"pce\": 7803.7,\n\"pop\": 291811,\n\"psavert\": 1.7,\n\"uempmed\": 10.2,\n\"unemploy\": 8928 \n},\n{\n \"date\": \"2003-09-30\",\n\"pce\": 7812.3,\n\"pop\": 292074,\n\"psavert\": 2.1,\n\"uempmed\": 10.4,\n\"unemploy\": 8731 \n},\n{\n \"date\": \"2003-10-31\",\n\"pce\": 7868.5,\n\"pop\": 292318,\n\"psavert\": 2.2,\n\"uempmed\": 10.3,\n\"unemploy\": 8590 \n},\n{\n \"date\": \"2003-11-30\",\n\"pce\": 7885.3,\n\"pop\": 292529,\n\"psavert\": 2.4,\n\"uempmed\": 10.4,\n\"unemploy\": 8338 \n},\n{\n \"date\": \"2003-12-31\",\n\"pce\": 7977.7,\n\"pop\": 292723,\n\"psavert\": 2.1,\n\"uempmed\": 10.6,\n\"unemploy\": 8367 \n},\n{\n \"date\": \"2004-01-31\",\n\"pce\": 8005.9,\n\"pop\": 292909,\n\"psavert\": 2.3,\n\"uempmed\": 10.2,\n\"unemploy\": 8171 \n},\n{\n \"date\": \"2004-02-29\",\n\"pce\": 8070.5,\n\"pop\": 293112,\n\"psavert\": 2,\n\"uempmed\": 10.2,\n\"unemploy\": 8452 \n},\n{\n \"date\": \"2004-03-31\",\n\"pce\": 8086.6,\n\"pop\": 293340,\n\"psavert\": 2.2,\n\"uempmed\": 9.5,\n\"unemploy\": 8155 \n},\n{\n \"date\": \"2004-04-30\",\n\"pce\": 8196.5,\n\"pop\": 293569,\n\"psavert\": 1.5,\n\"uempmed\": 9.9,\n\"unemploy\": 8197 \n},\n{\n \"date\": \"2004-05-31\",\n\"pce\": 8161.3,\n\"pop\": 293805,\n\"psavert\": 2.1,\n\"uempmed\": 10.9,\n\"unemploy\": 8259 \n},\n{\n \"date\": \"2004-06-30\",\n\"pce\": 8235.3,\n\"pop\": 294056,\n\"psavert\": 1.7,\n\"uempmed\": 8.9,\n\"unemploy\": 8163 \n},\n{\n \"date\": \"2004-07-31\",\n\"pce\": 8246.1,\n\"pop\": 294323,\n\"psavert\": 2,\n\"uempmed\": 9.3,\n\"unemploy\": 7993 \n},\n{\n \"date\": \"2004-08-31\",\n\"pce\": 8313.7,\n\"pop\": 294587,\n\"psavert\": 1.2,\n\"uempmed\": 9.6,\n\"unemploy\": 7953 \n},\n{\n \"date\": \"2004-09-30\",\n\"pce\": 8371.6,\n\"pop\": 294857,\n\"psavert\": 1.4,\n\"uempmed\": 9.5,\n\"unemploy\": 8052 \n},\n{\n \"date\": \"2004-10-31\",\n\"pce\": 8410.8,\n\"pop\": 295105,\n\"psavert\": 1.2,\n\"uempmed\": 9.7,\n\"unemploy\": 7950 \n},\n{\n \"date\": \"2004-11-30\",\n\"pce\": 8462,\n\"pop\": 295344,\n\"psavert\": 4.3,\n\"uempmed\": 9.4,\n\"unemploy\": 7997 \n},\n{\n \"date\": \"2004-12-31\",\n\"pce\": 8469.4,\n\"pop\": 295576,\n\"psavert\": 0.9,\n\"uempmed\": 9.4,\n\"unemploy\": 7756 \n},\n{\n \"date\": \"2005-01-31\",\n\"pce\": 8520.7,\n\"pop\": 295767,\n\"psavert\": 0.6,\n\"uempmed\": 9.1,\n\"unemploy\": 7966 \n},\n{\n \"date\": \"2005-02-28\",\n\"pce\": 8569,\n\"pop\": 295975,\n\"psavert\": 0.2,\n\"uempmed\": 9.2,\n\"unemploy\": 7683 \n},\n{\n \"date\": \"2005-03-31\",\n\"pce\": 8654.4,\n\"pop\": 296209,\n\"psavert\": -0.4,\n\"uempmed\": 9,\n\"unemploy\": 7657 \n},\n{\n \"date\": \"2005-04-30\",\n\"pce\": 8644.6,\n\"pop\": 296443,\n\"psavert\": -0.1,\n\"uempmed\": 9.1,\n\"unemploy\": 7656 \n},\n{\n \"date\": \"2005-05-31\",\n\"pce\": 8724.8,\n\"pop\": 296684,\n\"psavert\": -0.5,\n\"uempmed\": 9.2,\n\"unemploy\": 7507 \n},\n{\n \"date\": \"2005-06-30\",\n\"pce\": 8833.9,\n\"pop\": 296940,\n\"psavert\": -0.9,\n\"uempmed\": 9,\n\"unemploy\": 7464 \n},\n{\n \"date\": \"2005-07-31\",\n\"pce\": 8825.5,\n\"pop\": 297207,\n\"psavert\": -3,\n\"uempmed\": 9.2,\n\"unemploy\": 7360 \n},\n{\n \"date\": \"2005-08-31\",\n\"pce\": 8882.5,\n\"pop\": 297471,\n\"psavert\": -0.5,\n\"uempmed\": 8.5,\n\"unemploy\": 7606 \n},\n{\n \"date\": \"2005-09-30\",\n\"pce\": 8911.6,\n\"pop\": 297740,\n\"psavert\": -0.3,\n\"uempmed\": 8.6,\n\"unemploy\": 7436 \n},\n{\n \"date\": \"2005-10-31\",\n\"pce\": 8916.4,\n\"pop\": 297988,\n\"psavert\": -0.3,\n\"uempmed\": 8.4,\n\"unemploy\": 7548 \n},\n{\n \"date\": \"2005-11-30\",\n\"pce\": 8955.5,\n\"pop\": 298227,\n\"psavert\": -0.3,\n\"uempmed\": 8.5,\n\"unemploy\": 7331 \n},\n{\n \"date\": \"2005-12-31\",\n\"pce\": 9034.4,\n\"pop\": 298458,\n\"psavert\": -0.3,\n\"uempmed\": 8.5,\n\"unemploy\": 7023 \n},\n{\n \"date\": \"2006-01-31\",\n\"pce\": 9079.2,\n\"pop\": 298645,\n\"psavert\": -0.3,\n\"uempmed\": 8.9,\n\"unemploy\": 7158 \n},\n{\n \"date\": \"2006-02-28\",\n\"pce\": 9123.8,\n\"pop\": 298849,\n\"psavert\": -0.4,\n\"uempmed\": 8.5,\n\"unemploy\": 7009 \n},\n{\n \"date\": \"2006-03-31\",\n\"pce\": 9175.2,\n\"pop\": 299079,\n\"psavert\": -1,\n\"uempmed\": 8.5,\n\"unemploy\": 7098 \n},\n{\n \"date\": \"2006-04-30\",\n\"pce\": 9238.6,\n\"pop\": 299310,\n\"psavert\": -1.6,\n\"uempmed\": 8.5,\n\"unemploy\": 7006 \n},\n{\n \"date\": \"2006-05-31\",\n\"pce\": 9270.5,\n\"pop\": 299548,\n\"psavert\": -1.5,\n\"uempmed\": 7.6,\n\"unemploy\": 6984 \n},\n{\n \"date\": \"2006-06-30\",\n\"pce\": 9338.9,\n\"pop\": 299801,\n\"psavert\": -1.7,\n\"uempmed\": 8.2,\n\"unemploy\": 7228 \n},\n{\n \"date\": \"2006-07-31\",\n\"pce\": 9352.7,\n\"pop\": 300065,\n\"psavert\": -1.5,\n\"uempmed\": 8.4,\n\"unemploy\": 7116 \n},\n{\n \"date\": \"2006-08-31\",\n\"pce\": 9348.5,\n\"pop\": 300326,\n\"psavert\": -1,\n\"uempmed\": 8.1,\n\"unemploy\": 6912 \n},\n{\n \"date\": \"2006-09-30\",\n\"pce\": 9376,\n\"pop\": 300592,\n\"psavert\": -0.8,\n\"uempmed\": 8,\n\"unemploy\": 6715 \n},\n{\n \"date\": \"2006-10-31\",\n\"pce\": 9410.8,\n\"pop\": 300836,\n\"psavert\": -0.9,\n\"uempmed\": 8.2,\n\"unemploy\": 6826 \n},\n{\n \"date\": \"2006-11-30\",\n\"pce\": 9478.5,\n\"pop\": 301070,\n\"psavert\": -1.1,\n\"uempmed\": 7.3,\n\"unemploy\": 6849 \n},\n{\n \"date\": \"2006-12-31\",\n\"pce\": 9540.3,\n\"pop\": 301296,\n\"psavert\": -0.9,\n\"uempmed\": 8.1,\n\"unemploy\": 7017 \n},\n{\n \"date\": \"2007-01-31\",\n\"pce\": 9610.6,\n\"pop\": 301481,\n\"psavert\": -1,\n\"uempmed\": 8.1,\n\"unemploy\": 6865 \n},\n{\n \"date\": \"2007-02-28\",\n\"pce\": 9653,\n\"pop\": 301684,\n\"psavert\": -0.7,\n\"uempmed\": 8.5,\n\"unemploy\": 6724 \n},\n{\n \"date\": \"2007-03-31\",\n\"pce\": 9705,\n\"pop\": 301913,\n\"psavert\": -1.3,\n\"uempmed\": 8.7,\n\"unemploy\": 6801 \n} \n],\n\"pointSize\": 0,\n\"lineWidth\": 1,\n\"id\": \"chart3c7e79bc798\",\n\"labels\": [ \"psavert\", \"uempmed\" ] \n},\n chartType \u003d \"Line\"\n new Morris[chartType](chartParams)\n\u003c/script\u003e" }, - "dateCreated": "Feb 10, 2016 7:48:03 PM", - "dateStarted": "Feb 23, 2016 2:50:31 PM", - "dateFinished": "Feb 23, 2016 2:50:32 PM", + "dateCreated": "Feb 10, 2016 7:48:03 AM", + "dateStarted": "Feb 23, 2016 2:50:31 AM", + "dateFinished": "Feb 23, 2016 2:50:32 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { "text": "%r {\"imageWidth\": \"300px\"}\nlibrary(ggplot2)\npres_rating \u003c- data.frame(\n rating \u003d as.numeric(presidents),\n year \u003d as.numeric(floor(time(presidents))),\n quarter \u003d as.numeric(cycle(presidents))\n)\np \u003c- ggplot(pres_rating, aes(x\u003dyear, y\u003dquarter, fill\u003drating))\np + geom_raster()", - "dateUpdated": "Feb 23, 2016 2:50:32 PM", + "dateUpdated": "Feb 23, 2016 2:50:32 AM", "config": { "colWidth": 6.0, "graph": { @@ -961,6 +982,7 @@ "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1438930880648_-1572054429", "id": "20150807-090120_1060568667", "result": { @@ -969,13 +991,14 @@ "msg": "\u003cp\u003e\u003cimg src\u003d\"data:image/png;base64,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\u003d\" alt\u003d\"plot of chunk unnamed-chunk-1\" width\u003d\"300px\" /\u003e \u003c/p\u003e" }, "dateCreated": "Aug 7, 2015 9:01:20 AM", - "dateStarted": "Feb 23, 2016 2:50:32 PM", - "dateFinished": "Feb 23, 2016 2:50:33 PM", + "dateStarted": "Feb 23, 2016 2:50:32 AM", + "dateFinished": "Feb 23, 2016 2:50:33 AM", "status": "FINISHED", "progressUpdateIntervalMs": 500 }, { - "dateUpdated": "Feb 11, 2016 12:16:42 PM", + "text": "", + "dateUpdated": "Sep 2, 2016 10:37:44 AM", "config": { "colWidth": 12.0, "graph": { @@ -985,14 +1008,21 @@ "keys": [], "values": [], "groups": [], - "scatter": {} + "scatter": {}, + "map": { + "baseMapType": "Streets", + "isOnline": true, + "pinCols": [] + } }, - "enabled": true + "enabled": true, + "editorMode": "ace/mode/scala" }, "settings": { "params": {}, "forms": {} }, + "apps": [], "jobName": "paragraph_1455138857313_92355963", "id": "20160210-221417_1400405266", "result": { @@ -1000,7 +1030,7 @@ "type": "TEXT", "msg": "" }, - "dateCreated": "Feb 10, 2016 10:14:17 PM", + "dateCreated": "Feb 10, 2016 10:14:17 AM", "dateStarted": "Feb 11, 2016 12:16:57 PM", "dateFinished": "Feb 11, 2016 12:16:57 PM", "status": "FINISHED", @@ -1008,26 +1038,29 @@ } ], "name": "R Tutorial", - "id": "r", + "id": "2BWJFTXKJ", + "lastReplName": { + "value": "" + }, "angularObjects": { - "2BGNYGX62": [], - "2BH79J543": [], - "2BGEQ12SH": [], - "2BHEC1U86": [], - "2BFC8VSCU": [], - "2BEFFGZ94": [], - "2BHXPBPPB": [], - "2BEUFCFMV": [], - "2BEFB8NYY": [], - "2BE83RBH6": [], - "2BGHNUYS3": [], - "2BHWPPHUF": [], - "2BHZGMJB4": [], - "2BHRGU5SV": [], - "2BE19PPM5": [], - "2BF3RSAR9": [], - "2BFBDB2V5": [], - "2BHBNXNS5": [] + "2BTFWYU3G:shared_process": [], + "2BVBZ6JNU:shared_process": [], + "2BW7T15XK:shared_process": [], + "2BSWB77EY:shared_process": [], + "2BVRTYC2F:shared_process": [], + "2BV9E7JSD:shared_process": [], + "2BVX4ZVHR:shared_process": [], + "2BSN3TG5R:shared_process": [], + "2BW18CJ38:shared_process": [], + "2BW66P5BG:shared_process": [], + "2BTXCARSH:shared_process": [], + "2BU29UDME:shared_process": [], + "2BVY8EM4M:shared_process": [], + "2BV2T727F:shared_process": [], + "2BV3UXSHN:shared_process": [], + "2BSVRVBTS:shared_process": [], + "2BTWB3BK1:shared_process": [], + "2BW4AC8HE:shared_process": [] }, "config": { "looknfeel": "default" diff --git a/pom.xml b/pom.xml index 93e6ed93d10..2ac72cf3678 100644 --- a/pom.xml +++ b/pom.xml @@ -211,11 +211,6 @@ - - org.apache.rat - apache-rat-plugin - - maven-compiler-plugin 3.1 @@ -411,122 +406,6 @@ - - org.apache.rat - apache-rat-plugin - 0.11 - - - **/*.keywords - reports/** - **/.idea/ - **/*.iml - .git/ - .github/* - .gitignore - .repository/ - .Rhistory - **/*.diff - **/*.patch - **/*.avsc - **/*.avro - **/*.log - **/test/resources/** - **/.settings/* - **/.classpath - **/.project - **/target/** - **/derby.log - **/metastore_db/ - **/logs/** - **/run/** - **/interpreter/** - **/local-repo/** - **/null/** - **/notebook/** - _tools/site/css/* - **/README.md - DEPENDENCIES - DEPLOY.md - CONTRIBUTING.md - STYLE.md - Roadmap.md - **/licenses/** - **/zeppelin-distribution/src/bin_license/** - conf/interpreter.json - conf/notebook-authorization.json - conf/credentials.json - conf/zeppelin-env.sh - spark-*-bin*/** - .spark-dist/** - **/interpreter-setting.json - **/constants.json - - - docs/assets/themes/zeppelin/bootstrap/** - docs/assets/themes/zeppelin/css/style.css - docs/_includes/themes/zeppelin/_jumbotron.html - docs/_includes/themes/zeppelin/_navigation.html - - - docs/404.html - docs/_config.yml - docs/_includes/JB/** - docs/_layouts/** - docs/_plugins/** - docs/atom.xml - docs/_includes/themes/zeppelin/default.html - docs/_includes/themes/zeppelin/page.html - docs/_includes/themes/zeppelin/post.html - docs/_includes/themes/zeppelin/settings.yml - docs/Rakefile - docs/rss.xml - docs/sitemap.txt - **/dependency-reduced-pom.xml - - - docs/assets/themes/zeppelin/js/anchor.min.js - - - docs/assets/themes/zeppelin/js/toc.js - - - docs/assets/themes/zeppelin/css/syntax.css - - - docs/_site/** - docs/Gemfile.lock - - - R/lib/** - - - r/R/rzeppelin/R/globals.R - r/R/rzeppelin/R/common.R - r/R/rzeppelin/R/protocol.R - r/R/rzeppelin/R/rServer.R - r/R/rzeppelin/R/scalaInterpreter.R - r/R/rzeppelin/R/zzz.R - r/src/main/scala/scala/Console.scala - r/src/main/scala/org/apache/zeppelin/rinterpreter/rscala/Package.scala - r/src/main/scala/org/apache/zeppelin/rinterpreter/rscala/RClient.scala - - r/R/rzeppelin/DESCRIPTION - r/R/rzeppelin/NAMESPACE - - - - - - verify.rat - verify - - check - - - - - org.apache.maven.plugins maven-checkstyle-plugin @@ -652,7 +531,7 @@ scala-2.10 - !scala-2.11 + true 2.10.5 @@ -662,9 +541,6 @@ scala-2.11 - - scala-2.11 - 2.11.7 2.11 @@ -812,6 +688,143 @@ + + + rat + + !skipRat + + + + + org.apache.rat + apache-rat-plugin + 0.11 + + + **/*.keywords + reports/** + **/.idea/ + **/*.iml + .git/ + .github/* + .gitignore + .repository/ + .rat-excludes/ + .Rhistory + **/*.diff + **/*.patch + **/*.avsc + **/*.avro + **/*.log + **/test/resources/** + **/.settings/* + **/.classpath + **/.project + **/target/** + **/derby.log + **/metastore_db/ + **/logs/** + **/run/** + **/interpreter/** + **/local-repo/** + **/null/** + **/notebook/** + _tools/site/css/* + **/README.md + DEPENDENCIES + DEPLOY.md + CONTRIBUTING.md + STYLE.md + Roadmap.md + **/licenses/** + **/zeppelin-distribution/src/bin_license/** + conf/interpreter.json + conf/notebook-authorization.json + conf/credentials.json + conf/zeppelin-env.sh + spark-*-bin*/** + .spark-dist/** + **/interpreter-setting.json + **/constants.json + + + docs/assets/themes/zeppelin/bootstrap/** + docs/assets/themes/zeppelin/css/style.css + docs/assets/themes/zeppelin/js/docs.js + docs/assets/themes/zeppelin/js/search.js + docs/_includes/themes/zeppelin/_jumbotron.html + docs/_includes/themes/zeppelin/_navigation.html + + + docs/404.html + docs/_config.yml + docs/_includes/JB/** + docs/_layouts/** + docs/_plugins/** + docs/atom.xml + docs/_includes/themes/zeppelin/default.html + docs/_includes/themes/zeppelin/page.html + docs/_includes/themes/zeppelin/post.html + docs/_includes/themes/zeppelin/settings.yml + docs/Rakefile + docs/rss.xml + docs/sitemap.txt + docs/search_data.json + **/dependency-reduced-pom.xml + docs/CONTRIBUTING.md + + + docs/assets/themes/zeppelin/js/anchor.min.js + + + docs/assets/themes/zeppelin/js/toc.js + + + docs/assets/themes/zeppelin/js/lunr.min.js + + + docs/assets/themes/zeppelin/css/syntax.css + + + docs/_site/** + docs/Gemfile.lock + + **/horizontalbar_mockdata.txt + + + R/lib/** + r/lib/** + + + r/R/rzeppelin/R/globals.R + r/R/rzeppelin/R/common.R + r/R/rzeppelin/R/protocol.R + r/R/rzeppelin/R/rServer.R + r/R/rzeppelin/R/scalaInterpreter.R + r/R/rzeppelin/R/zzz.R + r/src/main/scala/scala/Console.scala + r/src/main/scala/org/apache/zeppelin/rinterpreter/rscala/Package.scala + r/src/main/scala/org/apache/zeppelin/rinterpreter/rscala/RClient.scala + + r/R/rzeppelin/DESCRIPTION + r/R/rzeppelin/NAMESPACE + + + + + + verify.rat + verify + + check + + + + + + + diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java b/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java index 670dffca39c..0561d86f867 100644 --- a/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java +++ b/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java @@ -134,10 +134,11 @@ public InterpreterResult interpret(String cmd, InterpreterContext contextInterpr InterpreterResult result; if (pythonErrorIn(output)) { - result = new InterpreterResult(Code.ERROR, output.replaceAll(">>>", "").trim()); + result = new InterpreterResult(Code.ERROR, output); } else { - result = new InterpreterResult(Code.SUCCESS, output.replaceAll(">>>", "") - .replaceAll("\\.\\.\\.", "").trim()); + // TODO(zjffdu), we should not do string replacement operation in the result, as it is + // possible that the output contains the kind of pattern itself, e.g. print("...") + result = new InterpreterResult(Code.SUCCESS, output.replaceAll("\\.\\.\\.", "")); } return result; } @@ -149,8 +150,17 @@ public InterpreterResult interpret(String cmd, InterpreterContext contextInterpr * @return true if syntax error or exception has happened */ private boolean pythonErrorIn(String output) { - Matcher errorMatcher = errorInLastLine.matcher(output); - return errorMatcher.find(); + boolean isError = false; + String[] outputMultiline = output.split("\n"); + Matcher errorMatcher; + for (String row : outputMultiline) { + errorMatcher = errorInLastLine.matcher(row); + if (errorMatcher.find() == true) { + isError = true; + break; + } + } + return isError; } @Override @@ -265,4 +275,5 @@ private int findRandomOpenPortOnAllLocalInterfaces() { public int getMaxResult() { return maxResult; } + } diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java index 348ced68a45..0ab14613101 100644 --- a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java +++ b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java @@ -21,12 +21,11 @@ import org.slf4j.LoggerFactory; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.InputStream; -import java.io.OutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.OutputStream; import java.lang.reflect.Field; /** @@ -34,11 +33,11 @@ * Python process (REPL) used by python interpreter */ public class PythonProcess { - Logger logger = LoggerFactory.getLogger(PythonProcess.class); - + private static final Logger logger = LoggerFactory.getLogger(PythonProcess.class); + private static final String STATEMENT_END = "*!?flush reader!?*"; InputStream stdout; OutputStream stdin; - BufferedWriter writer; + PrintWriter writer; BufferedReader reader; Process process; @@ -56,7 +55,7 @@ public void open() throws IOException { process = builder.start(); stdout = process.getInputStream(); stdin = process.getOutputStream(); - writer = new BufferedWriter(new OutputStreamWriter(stdin)); + writer = new PrintWriter(stdin, true); reader = new BufferedReader(new InputStreamReader(stdout)); try { pid = findPid(); @@ -85,22 +84,21 @@ public void interrupt() throws IOException { } public String sendAndGetResult(String cmd) throws IOException { - writer.write(cmd + "\n\n"); - writer.write("print (\"*!?flush reader!?*\")\n\n"); - writer.flush(); - - String output = ""; - String line; - while (!(line = reader.readLine()).contains("*!?flush reader!?*")) { + writer.println(cmd); + writer.println(); + writer.println("\"" + STATEMENT_END + "\""); + StringBuilder output = new StringBuilder(); + String line = null; + while (!(line = reader.readLine()).contains(STATEMENT_END)) { logger.debug("Read line from python shell : " + line); if (line.equals("...")) { logger.warn("Syntax error ! "); - output += "Syntax error ! "; + output.append("Syntax error ! "); break; } - output += "\r" + line + "\n"; + output.append(line + "\n"); } - return output; + return output.toString(); } private long findPid() throws NoSuchFieldException, IllegalAccessException { diff --git a/python/src/main/resources/bootstrap.py b/python/src/main/resources/bootstrap.py index ce28baf6ed9..235f7abfed8 100644 --- a/python/src/main/resources/bootstrap.py +++ b/python/src/main/resources/bootstrap.py @@ -19,90 +19,93 @@ # Remove interactive mode displayhook import sys import signal - +import base64 +from io import BytesIO try: - import StringIO as io + from StringIO import StringIO except ImportError: - import io as io - -sys.displayhook = lambda x: None + from io import StringIO def intHandler(signum, frame): # Set the signal handler print ("Paragraph interrupted") raise KeyboardInterrupt() signal.signal(signal.SIGINT, intHandler) - +# set prompt as empty string so that java side don't need to remove the prompt. +sys.ps1="" def help(): - print ('%html') - print ('

    Python Interpreter help

    ') - print ('

    Python 2 & 3 compatibility

    ') - print ('

    The interpreter is compatible with Python 2 & 3.
    ') - print ('To change Python version, ') - print ('change in the interpreter configuration the python to the ') - print ('desired version (example : python=/usr/bin/python3)

    ') - print ('

    Python modules

    ') - print ('

    The interpreter can use all modules already installed ') - print ('(with pip, easy_install, etc)

    ') - print ('

    Forms

    ') - print ('You must install py4j in order to use ' - 'the form feature (pip install py4j)') - print ('

    Input form

    ') - print ('
    print (z.input("f1","defaultValue"))
    ') - print ('

    Selection form

    ') - print ('
    print(z.select("f2", [("o1","1"), ("o2","2")],2))
    ') - print ('

    Checkbox form

    ') - print ('
     print("".join(z.checkbox("f3", [("o1","1"), '
    -           '("o2","2")],["1"])))
    ') - print ('

    Matplotlib graph

    ') - print ('
    The interpreter can display matplotlib graph with ') - print ('the function z.show()
    ') - print ('
    You need to already have matplotlib module installed ') - print ('to use this functionality !

    ') - print ('''
    import matplotlib.pyplot as plt
    -plt.figure()
    -(.. ..)
    -z.show(plt)
    -plt.close()
    -
    ''') - print ('

    z.show function can take optional parameters ') - print ('to adapt graph width and height
    ') - print ("
    example :") - print ('''
    z.show(plt,width='50px')
    -z.show(plt,height='150px') 
    ''') - print ('

    Pandas DataFrame

    ') - print ('
    You need to have Pandas module installed ') - print ('to use this functionality (pip install pandas) !

    ') - print (""" -
    The interpreter can visualize Pandas DataFrame -with the function z.show() -
    -import pandas as pd
    -df = pd.read_csv("bank.csv", sep=";")
    -z.show(df)
    -
    -""") - print ('

    SQL over Pandas DataFrame

    ') - print ('
    You need to have Pandas&Pandasql modules installed ') - print ('to use this functionality (pip install pandas pandasql) !

    ') - print (""" -
    Python interpreter group includes %sql interpreter that can query -Pandas DataFrames using SQL and visualize results using Zeppelin Table Display System - -
    -%python
    -import pandas as pd
    -df = pd.read_csv("bank.csv", sep=";")
    -
    -
    - -
    -%python.sql
    -%sql
    -SELECT * from df LIMIT 5
    -
    -""") + print("""%html +

    Python Interpreter help

    + +

    Python 2 & 3 compatibility

    +

    The interpreter is compatible with Python 2 & 3.
    + To change Python version, + change in the interpreter configuration the python to the + desired version (example : python=/usr/bin/python3)

    + +

    Python modules

    +

    The interpreter can use all modules already installed + (with pip, easy_install, etc)

    + +

    Forms

    + You must install py4j in order to use + the form feature (pip install py4j) +

    Input form

    +
    print (z.input("f1","defaultValue"))
    +

    Selection form

    +
    print(z.select("f2", [("o1","1"), ("o2","2")],2))
    +

    Checkbox form

    +
     print("".join(z.checkbox("f3", [("o1","1"), ("o2","2")],["1"])))
    ') + +

    Matplotlib graph

    +
    The interpreter can display matplotlib graph with + the function z.show()
    +
    You need to already have matplotlib module installed + to use this functionality !

    +
    import matplotlib.pyplot as plt
    + plt.figure()
    + (.. ..)
    + z.show(plt)
    + plt.close()
    + 
    +

    z.show function can take optional parameters + to adapt graph dimensions (width and height) and format (png or svg)
    +
    example : +
    z.show(plt,width='50px
    + z.show(plt,height='150px', fmt='svg') 
    + +

    Pandas DataFrame

    +
    You need to have Pandas module installed + to use this functionality (pip install pandas) !

    +
    The interpreter can visualize Pandas DataFrame + with the function z.show() +
    + import pandas as pd
    + df = pd.read_csv("bank.csv", sep=";")
    + z.show(df)
    + 
    + +

    SQL over Pandas DataFrame

    +
    You need to have Pandas&Pandasql modules installed + to use this functionality (pip install pandas pandasql) !

    + +
    Python interpreter group includes %sql interpreter that can query + Pandas DataFrames using SQL and visualize results using Zeppelin Table Display System + +
    + %python
    + import pandas as pd
    + df = pd.read_csv("bank.csv", sep=";")
    + 
    +
    +
    + %python.sql
    + %sql
    + SELECT * from df LIMIT 5
    + 
    +
    + """) class PyZeppelinContext(object): @@ -112,18 +115,17 @@ class PyZeppelinContext(object): errorMsg = "You must install py4j Python module " \ "(pip install py4j) to use Zeppelin dynamic forms features" - def __init__(self, zc): - self.z = zc + def __init__(self): self.max_result = 1000 def input(self, name, defaultValue=""): - print (self.errorMsg) + print(self.errorMsg) def select(self, name, options, defaultValue=""): - print (self.errorMsg) + print(self.errorMsg) def checkbox(self, name, options, defaultChecked=[]): - print (self.errorMsg) + print(self.errorMsg) def show(self, p, **kwargs): if hasattr(p, '__name__') and p.__name__ == "matplotlib.pyplot": @@ -139,20 +141,20 @@ def show_dataframe(self, df, **kwargs): """Pretty prints DF using Table Display System """ limit = len(df) > self.max_result - header_buf = io.StringIO("") - header_buf.write(df.columns[0]) + header_buf = StringIO("") + header_buf.write(str(df.columns[0])) for col in df.columns[1:]: header_buf.write("\t") - header_buf.write(col) + header_buf.write(str(col)) header_buf.write("\n") - body_buf = io.StringIO("") + body_buf = StringIO("") rows = df.head(self.max_result).values if limit else df.values for row in rows: - body_buf.write(row[0]) + body_buf.write(str(row[0])) for cell in row[1:]: body_buf.write("\t") - body_buf.write(cell) + body_buf.write(str(cell)) body_buf.write("\n") body_buf.seek(0); header_buf.seek(0) #TODO(bzz): fix it, so it shows red notice, as in Spark @@ -162,21 +164,29 @@ def show_dataframe(self, df, **kwargs): #) body_buf.close(); header_buf.close() - def show_matplotlib(self, p, width="0", height="0", **kwargs): + def show_matplotlib(self, p, fmt="png", width="auto", height="auto", + **kwargs): """Matplotlib show function """ - img = io.StringIO() - p.savefig(img, format='svg') - img.seek(0) - style = "" - if (width != "0"): - style += 'width:' + width - if (height != "0"): - if (len(style) != 0): - style += "," - style += 'height:' + height - print("%html
    " + img.read() + "
    ") + if fmt == "png": + img = BytesIO() + p.savefig(img, format=fmt) + img_str = b"data:image/png;base64," + img_str += base64.b64encode(img.getvalue().strip()) + img_tag = "" + # Decoding is necessary for Python 3 compability + img_str = img_str.decode("ascii") + img_str = img_tag.format(img=img_str, width=width, height=height) + elif fmt == "svg": + img = StringIO() + p.savefig(img, format=fmt) + img_str = img.getvalue() + else: + raise ValueError("fmt must be 'png' or 'svg'") + + html = "%html
    {img}
    " + print(html.format(width=width, height=height, img=img_str)) img.close() -z = PyZeppelinContext("") +z = PyZeppelinContext() diff --git a/python/src/main/resources/bootstrap_input.py b/python/src/main/resources/bootstrap_input.py index d15b93ad8c7..e006816256c 100644 --- a/python/src/main/resources/bootstrap_input.py +++ b/python/src/main/resources/bootstrap_input.py @@ -25,11 +25,11 @@ class Py4jZeppelinContext(PyZeppelinContext): """A context impl that uses Py4j to communicate to JVM """ - def __init__(self, zc): - super(Py4jZeppelinContext, self).__init__(zc) + def __init__(self, z): + self.z = z self.paramOption = gateway.jvm.org.apache.zeppelin.display.Input.ParamOption self.javaList = gateway.jvm.java.util.ArrayList - self.max_result = 1000 #TODO(bzz): read `zeppelin.python.maxResult` from JVM + self.max_result = self.z.getMaxResult() def input(self, name, defaultValue=""): return self.z.getGui().input(name, defaultValue) diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterPandasSqlTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterPandasSqlTest.java index 5f26adb2413..f9538562c22 100644 --- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterPandasSqlTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterPandasSqlTest.java @@ -153,4 +153,24 @@ public void badSqlSyntaxFails() { assertTrue(ret.message().length() > 0); } + @Test + public void showDataFrame() { + InterpreterResult ret; + ret = python.interpret("import pandas as pd", context); + ret = python.interpret("import numpy as np", context); + + // given a Pandas DataFrame with non-text data + ret = python.interpret("d1 = {1 : [np.nan, 1, 2, 3], 'two' : [3., 4., 5., 6.7]}", context); + ret = python.interpret("df1 = pd.DataFrame(d1)", context); + assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code()); + + // when + ret = python.interpret("z.show(df1)", context); + + // then + assertEquals(ret.message(), InterpreterResult.Code.SUCCESS, ret.code()); + assertEquals(ret.message(), Type.TABLE, ret.type()); + assertTrue(ret.message().indexOf("nan") > 0); + assertTrue(ret.message().indexOf("6.7") > 0); + } } diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java index a4c80ae8966..1228ec46a60 100644 --- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java @@ -115,7 +115,7 @@ public void testPy4jIsNotInstalled() { */ @Test public void testPy4jInstalled() throws IOException, InterruptedException { - when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn(">>>"); + when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn(""); pythonInterpreter.open(); Integer py4jPort = pythonInterpreter.getPy4jPort(); @@ -137,7 +137,7 @@ public void testPy4jInstalled() throws IOException, InterruptedException { @Test public void testClose() throws IOException, InterruptedException { //given: py4j is installed - when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn(">>>"); + when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn(""); pythonInterpreter.open(); Integer py4jPort = pythonInterpreter.getPy4jPort(); @@ -210,12 +210,33 @@ private String answerFromPythonMock(InvocationOnMock invocationOnMock) { String output = ""; for (int i = 0; i < lines.length; i++) { - output += ">>>" + lines[i]; + output += lines[i]; } return output; } else { - return ">>>"; + return ""; } } + @Test + public void checkMultiRowErrorFails() { + PythonInterpreter pythonInterpreter = new PythonInterpreter( + PythonInterpreterTest.getPythonTestProperties() + ); + pythonInterpreter.open(); + String codeRaiseException = "raise Exception(\"test exception\")"; + InterpreterResult ret = pythonInterpreter.interpret(codeRaiseException, null); + + assertNotNull("Interpreter result for raise exception is Null", ret); + + assertEquals(InterpreterResult.Code.ERROR, ret.code()); + assertTrue(ret.message().length() > 0); + + assertNotNull("Interpreter result for text is Null", ret); + String codePrintText = "print (\"Exception(\\\"test exception\\\")\")"; + ret = pythonInterpreter.interpret(codePrintText, null); + assertEquals(InterpreterResult.Code.SUCCESS, ret.code()); + assertTrue(ret.message().length() > 0); + } + } diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java index 15787fdce46..38b46e71ca8 100644 --- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java @@ -42,7 +42,7 @@ public class PythonInterpreterWithPythonInstalledTest { @Test - public void badSqlSyntaxFails() { + public void badPythonSyntaxFails() { //given PythonInterpreter realPython = new PythonInterpreter( PythonInterpreterTest.getPythonTestProperties()); @@ -58,4 +58,21 @@ public void badSqlSyntaxFails() { assertTrue(ret.message().length() > 0); } + @Test + public void goodPythonSyntaxRuns() { + //given + PythonInterpreter realPython = new PythonInterpreter( + PythonInterpreterTest.getPythonTestProperties()); + realPython.open(); + + //when + InterpreterResult ret = realPython.interpret("help()", null); + + //then + assertNotNull("Interpreter returned 'null'", ret); + //System.out.println("\nInterpreter response: \n" + ret.message()); + assertEquals(InterpreterResult.Code.SUCCESS, ret.code()); + assertTrue(ret.message().length() > 0); + } + } diff --git a/r/pom.xml b/r/pom.xml index 0e7d78deb61..2dc2eef5af7 100644 --- a/r/pom.xml +++ b/r/pom.xml @@ -380,7 +380,7 @@ scala-2.10 - !scala-2.11 + true src/main/scala-2.10 @@ -390,9 +390,6 @@ scala-2.11 - - scala-2.11 - src/main/scala-2.11 src/test/scala/scala-2.11 diff --git a/r/src/main/scala/org/apache/zeppelin/rinterpreter/RInterpreter.scala b/r/src/main/scala/org/apache/zeppelin/rinterpreter/RInterpreter.scala index 959f649a157..9504573f40e 100644 --- a/r/src/main/scala/org/apache/zeppelin/rinterpreter/RInterpreter.scala +++ b/r/src/main/scala/org/apache/zeppelin/rinterpreter/RInterpreter.scala @@ -17,6 +17,7 @@ package org.apache.zeppelin.rinterpreter +import java.io.{BufferedInputStream, File, FileInputStream} import java.nio.file.{Files, Paths} import java.util._ @@ -110,10 +111,10 @@ object RInterpreter { // These are the additional properties we need on top of the ones provided by the spark interpreters lazy val props: Map[String, InterpreterProperty] = new InterpreterPropertyBuilder() - .add("rhadoop.cmd", SparkInterpreter.getSystemDefault("rhadoop.cmd", "HADOOP_CMD", ""), "Usually /usr/bin/hadoop") - .add("rhadooop.streamingjar", SparkInterpreter.getSystemDefault("rhadoop.cmd", "HADOOP_STREAMING", ""), "Usually /usr/lib/hadoop/contrib/streaming/hadoop-streaming-.jar") - .add("rscala.debug", SparkInterpreter.getSystemDefault("rscala.debug","RSCALA_DEBUG", "false"), "Whether to turn on rScala debugging") // TEST: Implemented but not tested - .add("rscala.timeout", SparkInterpreter.getSystemDefault("rscala.timeout","RSCALA_TIMEOUT", "60"), "Timeout for rScala") // TEST: Implemented but not tested + .add("rhadoop.cmd", "HADOOP_CMD", "rhadoop.cmd", "", "Usually /usr/bin/hadoop") + .add("rhadooop.streamingjar", "HADOOP_STREAMING", "rhadooop.streamingjar", "", "Usually /usr/lib/hadoop/contrib/streaming/hadoop-streaming-.jar") + .add("rscala.debug", "RSCALA_DEBUG", "rscala.debug","false", "Whether to turn on rScala debugging") // TEST: Implemented but not tested + .add("rscala.timeout", "RSCALA_TIMEOUT", "rscala.timeout","60", "Timeout for rScala") // TEST: Implemented but not tested .build def getProps() = { @@ -141,8 +142,15 @@ object RInterpreter { } def dataURI(file : String, mime : String) : String = { - val data: String = Source.fromFile(file).getLines().mkString("\n") - s"""data:${mime};base64,""" + StringUtils.newStringUtf8(Base64.encodeBase64(data.getBytes(), false)) + val fp = new File(file) + val fdata = new Array[Byte](fp.length().toInt) + val fin = new BufferedInputStream(new FileInputStream(fp)) + try { + fin.read(fdata) + } finally { + fin.close() + } + s"""data:${mime};base64,""" + StringUtils.newStringUtf8(Base64.encodeBase64(fdata, false)) } // The purpose here is to deal with knitr producing HTML with script and css tags outside the diff --git a/scripts/docker/spark-cluster-managers/spark_mesos/Dockerfile b/scripts/docker/spark-cluster-managers/spark_mesos/Dockerfile new file mode 100644 index 00000000000..450afef94f8 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_mesos/Dockerfile @@ -0,0 +1,63 @@ +# 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. +FROM centos:centos6 + +ENV SPARK_PROFILE 2.0 +ENV SPARK_VERSION 2.0.0 +ENV HADOOP_PROFILE 2.3 +ENV HADOOP_VERSION 2.3.0 + +# Update the image with the latest packages +RUN yum update -y; yum clean all + +# Get utils +RUN yum install -y \ +wget \ +tar \ +curl \ +svn \ +cyrus-sasl-md5 \ +libevent2-devel \ +&& \ +yum clean all + +# Remove old jdk +RUN yum remove java; yum remove jdk + +# install jdk7 +RUN yum install -y java-1.7.0-openjdk-devel +ENV JAVA_HOME /usr/lib/jvm/java +ENV PATH $PATH:$JAVA_HOME/bin + +# install spark +RUN curl -s http://www.apache.org/dist/spark/spark-$SPARK_VERSION/spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE.tgz | tar -xz -C /usr/local/ +RUN cd /usr/local && ln -s spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE spark + +# update boot script +COPY entrypoint.sh /etc/entrypoint.sh +RUN chown root.root /etc/entrypoint.sh +RUN chmod 700 /etc/entrypoint.sh + +# install mesos +RUN wget http://repos.mesosphere.com/el/6/x86_64/RPMS/mesos-1.0.0-2.0.89.centos65.x86_64.rpm +RUN rpm -Uvh mesos-1.0.0-2.0.89.centos65.x86_64.rpm + +#spark +EXPOSE 8080 7077 7072 8081 8082 + +#mesos +EXPOSE 5050 5051 + +ENTRYPOINT ["/etc/entrypoint.sh"] diff --git a/scripts/docker/spark-cluster-managers/spark_mesos/entrypoint.sh b/scripts/docker/spark-cluster-managers/spark_mesos/entrypoint.sh new file mode 100755 index 00000000000..28d76bf9a1e --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_mesos/entrypoint.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# 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. + +export SPARK_HOME=/usr/local/spark/ +export SPARK_MASTER_PORT=7077 +export SPARK_MASTER_WEBUI_PORT=8080 +export SPARK_WORKER_PORT=8888 +export SPARK_WORKER_WEBUI_PORT=8081 +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/server/ + +# spark configuration +cp $SPARK_HOME/conf/spark-env.sh.template $SPARK_HOME/conf/spark-env.sh +echo "export MESOS_NATIVE_JAVA_LIBRARY=/usr/lib/libmesos.so" >> $SPARK_HOME/conf/spark-env.sh + +cp $SPARK_HOME/conf/spark-defaults.conf.template $SPARK_HOME/conf/spark-defaults.conf +echo "spark.master mesos://`hostname`:5050" >> $SPARK_HOME/conf/spark-defaults.conf +echo "spark.mesos.executor.home /usr/local/spark" >> $SPARK_HOME/conf/spark-defaults.conf + +# run spark +cd $SPARK_HOME/sbin +./start-master.sh +./start-slave.sh spark://`hostname`:$SPARK_MASTER_PORT + +# start mesos +mesos-master --ip=0.0.0.0 --work_dir=/var/lib/mesos &> /var/log/mesos_master.log & +mesos-slave --master=0.0.0.0:5050 --work_dir=/var/lib/mesos --launcher=posix &> /var/log/mesos_slave.log & + +CMD=${1:-"exit 0"} +if [[ "$CMD" == "-d" ]]; +then + service sshd stop + /usr/sbin/sshd -D -d +else + /bin/bash -c "$*" +fi diff --git a/scripts/docker/spark-cluster-managers/spark_standalone/Dockerfile b/scripts/docker/spark-cluster-managers/spark_standalone/Dockerfile new file mode 100644 index 00000000000..fa3078bd75b --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_standalone/Dockerfile @@ -0,0 +1,53 @@ +# 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. +FROM centos:centos6 + +ENV SPARK_PROFILE 1.6 +ENV SPARK_VERSION 1.6.2 +ENV HADOOP_PROFILE 2.3 +ENV SPARK_HOME /usr/local/spark + +# Update the image with the latest packages +RUN yum update -y; yum clean all + +# Get utils +RUN yum install -y \ +wget \ +tar \ +curl \ +&& \ +yum clean all + +# Remove old jdk +RUN yum remove java; yum remove jdk + +# install jdk7 +RUN yum install -y java-1.7.0-openjdk-devel +ENV JAVA_HOME /usr/lib/jvm/java +ENV PATH $PATH:$JAVA_HOME/bin + +# install spark +RUN curl -s http://apache.mirror.cdnetworks.com/spark/spark-$SPARK_VERSION/spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE.tgz | tar -xz -C /usr/local/ +RUN cd /usr/local && ln -s spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE spark + +# update boot script +COPY entrypoint.sh /etc/entrypoint.sh +RUN chown root.root /etc/entrypoint.sh +RUN chmod 700 /etc/entrypoint.sh + +#spark +EXPOSE 8080 7077 8888 8081 + +ENTRYPOINT ["/etc/entrypoint.sh"] diff --git a/scripts/docker/spark-cluster-managers/spark_standalone/entrypoint.sh b/scripts/docker/spark-cluster-managers/spark_standalone/entrypoint.sh new file mode 100755 index 00000000000..f4fded0dc9e --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_standalone/entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# 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. + +export SPARK_MASTER_PORT=7077 + +# run spark +cd /usr/local/spark/sbin +./start-master.sh +./start-slave.sh spark://`hostname`:$SPARK_MASTER_PORT + +CMD=${1:-"exit 0"} +if [[ "$CMD" == "-d" ]]; +then + service sshd stop + /usr/sbin/sshd -D -d +else + /bin/bash -c "$*" +fi diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/Dockerfile b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/Dockerfile new file mode 100644 index 00000000000..712c5b270f9 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/Dockerfile @@ -0,0 +1,107 @@ +# 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. +FROM centos:centos6 + +ENV SPARK_PROFILE 2.0 +ENV SPARK_VERSION 2.0.0 +ENV HADOOP_PROFILE 2.7 +ENV HADOOP_VERSION 2.7.0 + +# Update the image with the latest packages +RUN yum update -y; yum clean all + +# Get utils +RUN yum install -y \ +wget \ +tar \ +curl \ +&& \ +yum clean all + +# Remove old jdk +RUN yum remove java; yum remove jdk + +# install jdk7 +RUN yum install -y java-1.7.0-openjdk-devel +ENV JAVA_HOME /usr/lib/jvm/java +ENV PATH $PATH:$JAVA_HOME/bin + +# install hadoop +RUN yum install -y curl which tar sudo openssh-server openssh-clients rsync + +# hadoop +RUN curl -s https://archive.apache.org/dist/hadoop/core/hadoop-$HADOOP_VERSION/hadoop-$HADOOP_VERSION.tar.gz | tar -xz -C /usr/local/ +RUN cd /usr/local && ln -s ./hadoop-$HADOOP_VERSION hadoop + +ENV HADOOP_PREFIX /usr/local/hadoop +ENV HADOOP_COMMON_HOME /usr/local/hadoop +ENV HADOOP_HDFS_HOME /usr/local/hadoop +ENV HADOOP_MAPRED_HOME /usr/local/hadoop +ENV HADOOP_YARN_HOME /usr/local/hadoop +ENV HADOOP_CONF_DIR /usr/local/hadoop/etc/hadoop + +RUN sed -i '/^export JAVA_HOME/ s:.*:export JAVA_HOME=/usr/lib/jvm/jre-1.7.0-openjdk.x86_64\nexport HADOOP_PREFIX=/usr/local/hadoop\nexport HADOOP_HOME=/usr/local/hadoop\n:' $HADOOP_PREFIX/etc/hadoop/hadoop-env.sh +RUN sed -i '/^export HADOOP_CONF_DIR/ s:.*:export HADOOP_CONF_DIR=/usr/local/hadoop/etc/hadoop/:' $HADOOP_PREFIX/etc/hadoop/hadoop-env.sh + +RUN mkdir $HADOOP_PREFIX/input +RUN cp $HADOOP_PREFIX/etc/hadoop/*.xml $HADOOP_PREFIX/input + +# hadoop configurations +ADD hdfs_conf/core-site.xml $HADOOP_PREFIX/etc/hadoop/core-site.xml +ADD hdfs_conf/hdfs-site.xml $HADOOP_PREFIX/etc/hadoop/hdfs-site.xml +ADD hdfs_conf/mapred-site.xml $HADOOP_PREFIX/etc/hadoop/mapred-site.xml +ADD hdfs_conf/yarn-site.xml $HADOOP_PREFIX/etc/hadoop/yarn-site.xml + +RUN mkdir /data/ +RUN chmod 777 /data/ +RUN $HADOOP_PREFIX/bin/hdfs namenode -format + +RUN rm /usr/local/hadoop/lib/native/* +RUN curl -Ls http://dl.bintray.com/sequenceiq/sequenceiq-bin/hadoop-native-64-$HADOOP_VERSION.tar|tar -x -C /usr/local/hadoop/lib/native/ + +# install spark +RUN curl -s http://archive.apache.org/dist/spark/spark-$SPARK_VERSION/spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE.tgz | tar -xz -C /usr/local/ +RUN cd /usr/local && ln -s spark-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE spark +ENV SPARK_HOME /usr/local/spark + +ENV YARN_CONF_DIR $HADOOP_PREFIX/etc/hadoop +ENV PATH $PATH:$SPARK_HOME/bin:$HADOOP_PREFIX/bin + +# passwordless ssh +RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key +RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key +RUN ssh-keygen -q -N "" -t rsa -f /root/.ssh/id_rsa +RUN cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys + +ADD ssh_config /root/.ssh/config +RUN chmod 600 /root/.ssh/config +RUN chown root:root /root/.ssh/config +RUN chmod +x /usr/local/hadoop/etc/hadoop/*-env.sh + +# update boot script +COPY entrypoint.sh /etc/entrypoint.sh +RUN chown root.root /etc/entrypoint.sh +RUN chmod 700 /etc/entrypoint.sh + +# Hdfs ports +EXPOSE 50010 50020 50070 50075 50090 +# Mapred ports +EXPOSE 9000 9001 +#Yarn ports +EXPOSE 8030 8031 8032 8033 8040 8042 8088 +#spark +EXPOSE 8080 7077 8888 8081 + +ENTRYPOINT ["/etc/entrypoint.sh"] diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/entrypoint.sh b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/entrypoint.sh new file mode 100755 index 00000000000..85b335d6d12 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# 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. + +echo 'hadoop' |passwd root --stdin + +: ${HADOOP_PREFIX:=/usr/local/hadoop} + +$HADOOP_PREFIX/etc/hadoop/hadoop-env.sh + +rm /tmp/*.pid + +# installing libraries if any - (resource urls added comma separated to the ACP system variable) +cd $HADOOP_PREFIX/share/hadoop/common ; for cp in ${ACP//,/ }; do echo == $cp; curl -LO $cp ; done; cd - + +cp $SPARK_HOME/conf/metrics.properties.template $SPARK_HOME/conf/metrics.properties + +# start hadoop +service sshd start +$HADOOP_PREFIX/sbin/start-dfs.sh +$HADOOP_PREFIX/sbin/start-yarn.sh + +$HADOOP_PREFIX/bin/hdfs dfsadmin -safemode leave && $HADOOP_PREFIX/bin/hdfs dfs -put $SPARK_HOME-$SPARK_VERSION-bin-hadoop$HADOOP_PROFILE/lib /spark + +# start spark +export SPARK_MASTER_OPTS="-Dspark.driver.port=7001 -Dspark.fileserver.port=7002 + -Dspark.broadcast.port=7003 -Dspark.replClassServer.port=7004 + -Dspark.blockManager.port=7005 -Dspark.executor.port=7006 + -Dspark.ui.port=4040 -Dspark.broadcast.factory=org.apache.spark.broadcast.HttpBroadcastFactory" +export SPARK_WORKER_OPTS="-Dspark.driver.port=7001 -Dspark.fileserver.port=7002 + -Dspark.broadcast.port=7003 -Dspark.replClassServer.port=7004 + -Dspark.blockManager.port=7005 -Dspark.executor.port=7006 + -Dspark.ui.port=4040 -Dspark.broadcast.factory=org.apache.spark.broadcast.HttpBroadcastFactory" + +export SPARK_MASTER_PORT=7077 + +cd /usr/local/spark/sbin +./start-master.sh +./start-slave.sh spark://`hostname`:$SPARK_MASTER_PORT + +CMD=${1:-"exit 0"} +if [[ "$CMD" == "-d" ]]; +then + service sshd stop + /usr/sbin/sshd -D -d +else + /bin/bash -c "$*" +fi diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/core-site.xml b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/core-site.xml new file mode 100644 index 00000000000..87446337f9a --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/core-site.xml @@ -0,0 +1,22 @@ + + + + fs.defaultFS + hdfs://0.0.0.0:9000 + + diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/hdfs-site.xml b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/hdfs-site.xml new file mode 100644 index 00000000000..b3f88af304c --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/hdfs-site.xml @@ -0,0 +1,78 @@ + + + + dfs.replication + 1 + + + + dfs.data.dir + /data/hdfs + true + + + + dfs.permissions + false + + + + dfs.client.use.datanode.hostname + true + Whether clients should use datanode hostnames when + connecting to datanodes. + + + + + dfs.datanode.use.datanode.hostname + true + Whether datanodes should use datanode hostnames when + connecting to other datanodes for data transfer. + + + + + dfs.datanode.address + 0.0.0.0:50010 + + The address where the datanode server will listen to. + If the port is 0 then the server will start on a free port. + + + + + dfs.datanode.http.address + 0.0.0.0:50075 + + The datanode http server address and port. + If the port is 0 then the server will start on a free port. + + + + + dfs.datanode.ipc.address + 0.0.0.0:50020 + + The datanode ipc server address and port. + If the port is 0 then the server will start on a free port. + + + + + diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/mapred-site.xml b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/mapred-site.xml new file mode 100644 index 00000000000..f8280f74657 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/mapred-site.xml @@ -0,0 +1,22 @@ + + + + mapreduce.framework.name + yarn + + diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/yarn-site.xml b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/yarn-site.xml new file mode 100644 index 00000000000..89848165c77 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/hdfs_conf/yarn-site.xml @@ -0,0 +1,42 @@ + + + + yarn.resourcemanager.scheduler.address + 0.0.0.0:8030 + + + yarn.resourcemanager.address + 0.0.0.0:8032 + + + yarn.resourcemanager.webapp.address + 0.0.0.0:8088 + + + yarn.resourcemanager.resource-tracker.address + 0.0.0.0:8031 + + + yarn.resourcemanager.admin.address + 0.0.0.0:8033 + + + yarn.application.classpath + /usr/local/hadoop/etc/hadoop, /usr/local/hadoop/share/hadoop/common/*, /usr/local/hadoop/share/hadoop/common/lib/*, /usr/local/hadoop/share/hadoop/hdfs/*, /usr/local/hadoop/share/hadoop/hdfs/lib/*, /usr/local/hadoop/share/hadoop/mapreduce/*, /usr/local/hadoop/share/hadoop/mapreduce/lib/*, /usr/local/hadoop/share/hadoop/yarn/*, /usr/local/hadoop/share/hadoop/yarn/lib/*, /usr/local/hadoop/share/spark/* + + diff --git a/scripts/docker/spark-cluster-managers/spark_yarn_cluster/ssh_config b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/ssh_config new file mode 100644 index 00000000000..537a95f48e5 --- /dev/null +++ b/scripts/docker/spark-cluster-managers/spark_yarn_cluster/ssh_config @@ -0,0 +1,18 @@ +# 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. +Host * + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + LogLevel quiet \ No newline at end of file diff --git a/scripts/vagrant/zeppelin-dev/roles/maven/tasks/main.yml b/scripts/vagrant/zeppelin-dev/roles/maven/tasks/main.yml index e435aac5361..0e21e76723c 100644 --- a/scripts/vagrant/zeppelin-dev/roles/maven/tasks/main.yml +++ b/scripts/vagrant/zeppelin-dev/roles/maven/tasks/main.yml @@ -23,24 +23,24 @@ - name: Call apache web service to find preferred maven download mirror - uri: url=http://www.apache.org/dyn/closer.cgi?path=maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz&asjson=1 return_content=yes + uri: url=http://www.apache.org/dyn/closer.cgi?path=maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz&asjson=1 return_content=yes register: webResponse #- debug: var=webResponse - name: download maven get_url: url="{{webResponse.json.preferred}}{{webResponse.json.path_info}}" - dest=/tmp/apache-maven-3.3.3-bin.tar.gz + dest=/tmp/apache-maven-3.3.9-bin.tar.gz mode=0440 validate_certs=False - name: extract maven tgz - unarchive: src=/tmp/apache-maven-3.3.3-bin.tar.gz + unarchive: src=/tmp/apache-maven-3.3.9-bin.tar.gz dest=/usr/local/ copy=no - creates=/usr/local/apache-maven-3.3.3 + creates=/usr/local/apache-maven-3.3.9 - name: create symlink to this maven version - file: src=/usr/local/apache-maven-3.3.3/bin/mvn + file: src=/usr/local/apache-maven-3.3.9/bin/mvn path=/usr/bin/mvn state=link diff --git a/spark-dependencies/pom.xml b/spark-dependencies/pom.xml index 7da976adeea..f320680072b 100644 --- a/spark-dependencies/pom.xml +++ b/spark-dependencies/pom.xml @@ -959,26 +959,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - **/derby.log - **/metastore_db/ - **/README.md - dependency-reduced-pom.xml - - - - maven-enforcer-plugin 1.3.1 diff --git a/spark/pom.xml b/spark/pom.xml index eb208fd1ff9..66d93c42ee6 100644 --- a/spark/pom.xml +++ b/spark/pom.xml @@ -51,11 +51,11 @@ slf4j-log4j12 - + ${project.groupId} @@ -301,27 +301,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - **/derby.log - **/metastore_db/ - **/README.md - **/dependency-reduced-pom.xml - **/interpreter-setting.json - - - - maven-enforcer-plugin 1.3.1 @@ -434,11 +413,194 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/SparkRInterpreter.java + + + **/SparkRInterpreterTest.java + **/ZeppelinRTest.java + + + + + org.scala-tools + maven-scala-plugin + + + **/ZeppelinR.scala + **/SparkRBackend.scala + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/SparkRInterpreterTest.java + + + - + + spark-1.1 + + + + + 1.1.1 + 2.2.3-shaded-protobuf + + + + + spark-1.2 + + + + 1.2.1 + + + + + spark-1.3 + + + 1.3.1 + + + + + + + + + spark-1.4 + + 1.4.1 + + + + + + + + spark-1.5 + + 1.5.2 + com.typesafe.akka + 2.3.11 + 2.5.0 + + + + + spark-1.6 + + 1.6.1 + 0.9 + com.typesafe.akka + 2.3.11 + 2.5.0 + + + + + spark-2.0 + + true + + + 2.0.0 + 2.5.0 + 0.10.1 + 2.11.8 + + + + + hadoop-0.23 + + + + org.apache.avro + avro + + + + 0.23.10 + + + + + hadoop-1 + + 1.0.4 + hadoop1 + 1.8.8 + org.spark-project.akka + + + + + hadoop-2.2 + + 2.2.0 + 2.5.0 + hadoop2 + + + + + hadoop-2.3 + + 2.3.0 + 2.5.0 + 0.9.3 + hadoop2 + + + + + hadoop-2.4 + + 2.4.0 + 2.5.0 + 0.9.3 + hadoop2 + + + + + hadoop-2.6 + + 2.6.0 + 2.5.0 + 0.9.3 + hadoop2 + + + + + hadoop-2.7 + + 2.7.2 + 2.5.0 + 0.9.0 + hadoop2 + + + + sparkr @@ -453,36 +615,21 @@ src/main/sparkr-resources - - - - exclude-sparkr - - true - - org.apache.maven.plugins maven-compiler-plugin - - **/SparkRInterpreter.java - - - **/SparkRInterpreterTest.java - **/ZeppelinRTest.java - + + org.scala-tools maven-scala-plugin - - **/ZeppelinR.scala - **/SparkRBackend.scala + @@ -490,8 +637,7 @@ org.apache.maven.plugins maven-surefire-plugin - - **/SparkRInterpreterTest.java + @@ -499,5 +645,4 @@ - diff --git a/spark/src/main/java/org/apache/zeppelin/spark/LogOutputStream.java b/spark/src/main/java/org/apache/zeppelin/spark/LogOutputStream.java new file mode 100644 index 00000000000..d941cd772c6 --- /dev/null +++ b/spark/src/main/java/org/apache/zeppelin/spark/LogOutputStream.java @@ -0,0 +1,116 @@ +/* + * 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.spark; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + +/** + * Minor modification of LogOutputStream of apache commons exec. + * LogOutputStream of apache commons exec has one issue that method flush doesn't throw IOException, + * so that SparkOutputStream can not extend it correctly. + */ +public abstract class LogOutputStream extends OutputStream { + private static final int INTIAL_SIZE = 132; + private static final int CR = 13; + private static final int LF = 10; + private final ByteArrayOutputStream buffer; + private boolean skip; + private final int level; + + public LogOutputStream() { + this(999); + } + + public LogOutputStream(int level) { + this.buffer = new ByteArrayOutputStream(132); + this.skip = false; + this.level = level; + } + + @Override + public void write(int cc) throws IOException { + byte c = (byte) cc; + if (c != 10 && c != 13) { + this.buffer.write(cc); + } else if (!this.skip) { + this.processBuffer(); + } + + this.skip = c == 13; + } + + @Override + public void flush() throws IOException { + if (this.buffer.size() > 0) { + this.processBuffer(); + } + + } + + @Override + public void close() throws IOException { + if (this.buffer.size() > 0) { + this.processBuffer(); + } + + super.close(); + } + + public int getMessageLevel() { + return this.level; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int offset = off; + int blockStartOffset = off; + + for (int remaining = len; remaining > 0; blockStartOffset = offset) { + while (remaining > 0 && b[offset] != 10 && b[offset] != 13) { + ++offset; + --remaining; + } + + int blockLength = offset - blockStartOffset; + if (blockLength > 0) { + this.buffer.write(b, blockStartOffset, blockLength); + } + + while (remaining > 0 && (b[offset] == 10 || b[offset] == 13)) { + this.write(b[offset]); + ++offset; + --remaining; + } + } + + } + + protected void processBuffer() { + this.processLine(this.buffer.toString()); + this.buffer.reset(); + } + + protected void processLine(String line) { + this.processLine(line, this.level); + } + + protected abstract void processLine(String var1, int var2); +} diff --git a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java index f63f3d4edf8..ed85558028d 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java @@ -75,6 +75,7 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand private ByteArrayOutputStream input; private String scriptPath; boolean pythonscriptRunning = false; + private static final int MAX_TIMEOUT_SEC = 10; public PySparkInterpreter(Properties property) { super(property); @@ -179,7 +180,7 @@ private void createGatewayServerAndStartScript() { cmd.addArgument(Integer.toString(port), false); cmd.addArgument(Integer.toString(getSparkInterpreter().getSparkVersion().toNumber()), false); executor = new DefaultExecutor(); - outputStream = new SparkOutputStream(); + outputStream = new SparkOutputStream(logger); PipedOutputStream ps = new PipedOutputStream(); in = null; try { @@ -316,7 +317,7 @@ public InterpreterResult interpret(String st, InterpreterContext context) { long startTime = System.currentTimeMillis(); while (pythonScriptInitialized == false && pythonscriptRunning - && System.currentTimeMillis() - startTime < 10 * 1000) { + && System.currentTimeMillis() - startTime < MAX_TIMEOUT_SEC * 1000) { try { pythonScriptInitializeNotifier.wait(1000); } catch (InterruptedException e) { @@ -423,8 +424,15 @@ public List completion(String buf, int cursor) { } synchronized (statementFinishedNotifier) { - while (statementOutput == null) { + long startTime = System.currentTimeMillis(); + while (statementOutput == null + && pythonScriptInitialized == false + && pythonscriptRunning) { try { + if (System.currentTimeMillis() - startTime < MAX_TIMEOUT_SEC * 1000) { + logger.error("pyspark completion didn't have response for {}sec.", MAX_TIMEOUT_SEC); + break; + } statementFinishedNotifier.wait(1000); } catch (InterruptedException e) { // not working diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java index 29c322d78d6..9a54912a35c 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java @@ -33,6 +33,7 @@ import com.google.common.base.Joiner; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.security.UserGroupInformation; import org.apache.spark.SparkConf; import org.apache.spark.SparkContext; @@ -48,6 +49,7 @@ import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterException; +import org.apache.zeppelin.interpreter.InterpreterProperty; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.InterpreterUtils; @@ -67,10 +69,8 @@ import scala.collection.Iterator; import scala.collection.JavaConversions; import scala.collection.JavaConverters; -import scala.collection.convert.WrapAsJava; import scala.collection.Seq; import scala.collection.convert.WrapAsJava$; -import scala.collection.convert.WrapAsScala; import scala.collection.mutable.HashMap; import scala.collection.mutable.HashSet; import scala.reflect.io.AbstractFile; @@ -114,7 +114,7 @@ public class SparkInterpreter extends Interpreter { /** * completer - org.apache.spark.repl.SparkJLineCompletion (scala 2.10) */ - private Object completer; + private Object completer = null; private Map binder; private SparkVersion sparkVersion; @@ -124,7 +124,7 @@ public class SparkInterpreter extends Interpreter { public SparkInterpreter(Properties property) { super(property); - out = new SparkOutputStream(); + out = new SparkOutputStream(logger); } public SparkInterpreter(Properties property, SparkContext sc) { @@ -238,10 +238,7 @@ public SQLContext getSQLContext() { */ private SQLContext getSQLContext_2() { if (sqlc == null) { - sqlc = (SQLContext) Utils.invokeMethod(sparkSession, "wrapped"); - if (sqlc == null) { - sqlc = (SQLContext) Utils.invokeMethod(sparkSession, "sqlContext"); - } + sqlc = (SQLContext) Utils.invokeMethod(sparkSession, "sqlContext"); } return sqlc; } @@ -305,7 +302,9 @@ public Object createSparkSession() { String execUri = System.getenv("SPARK_EXECUTOR_URI"); conf.setAppName(getProperty("spark.app.name")); - conf.set("spark.repl.class.outputDir", outputDir.getAbsolutePath()); + if (outputDir != null) { + conf.set("spark.repl.class.outputDir", outputDir.getAbsolutePath()); + } if (execUri != null) { conf.set("spark.executor.uri", execUri); @@ -329,6 +328,7 @@ public Object createSparkSession() { } } + setupConfForPySpark(conf); Class SparkSession = Utils.findClass("org.apache.spark.sql.SparkSession"); Object builder = Utils.invokeStaticMethod(SparkSession, "builder"); Utils.invokeMethod(builder, "config", new Class[]{ SparkConf.class }, new Object[]{ conf }); @@ -442,12 +442,17 @@ public SparkContext createSparkContext_1() { conf.set(key, val); } } + setupConfForPySpark(conf); + SparkContext sparkContext = new SparkContext(conf); + return sparkContext; + } - //TODO(jongyoul): Move these codes into PySparkInterpreter.java - String pysparkBasePath = getSystemDefault("SPARK_HOME", null, null); + private void setupConfForPySpark(SparkConf conf) { + String pysparkBasePath = new InterpreterProperty("SPARK_HOME", null, null, null).getValue(); File pysparkPath; if (null == pysparkBasePath) { - pysparkBasePath = getSystemDefault("ZEPPELIN_HOME", "zeppelin.home", "../"); + pysparkBasePath = + new InterpreterProperty("ZEPPELIN_HOME", "zeppelin.home", "../", null).getValue(); pysparkPath = new File(pysparkBasePath, "interpreter" + File.separator + "spark" + File.separator + "pyspark"); } else { @@ -456,7 +461,8 @@ public SparkContext createSparkContext_1() { } //Only one of py4j-0.9-src.zip and py4j-0.8.2.1-src.zip should exist - String[] pythonLibs = new String[]{"pyspark.zip", "py4j-0.9-src.zip", "py4j-0.8.2.1-src.zip"}; + String[] pythonLibs = new String[]{"pyspark.zip", "py4j-0.9-src.zip", "py4j-0.8.2.1-src.zip", + "py4j-0.10.1-src.zip"}; ArrayList pythonLibUris = new ArrayList<>(); for (String lib : pythonLibs) { File libFile = new File(pysparkPath, lib); @@ -486,9 +492,6 @@ public SparkContext createSparkContext_1() { if (getProperty("master").equals("yarn-client")) { conf.set("spark.yarn.isPython", "true"); } - - SparkContext sparkContext = new SparkContext(conf); - return sparkContext; } static final String toString(Object o) { @@ -499,27 +502,6 @@ private boolean useSparkSubmit() { return null != System.getenv("SPARK_SUBMIT"); } - public static String getSystemDefault( - String envName, - String propertyName, - String defaultValue) { - - if (envName != null && !envName.isEmpty()) { - String envValue = System.getenv().get(envName); - if (envValue != null) { - return envValue; - } - } - - if (propertyName != null && !propertyName.isEmpty()) { - String propValue = System.getProperty(propertyName); - if (propValue != null) { - return propValue; - } - } - return defaultValue; - } - public boolean printREPLOutput() { return java.lang.Boolean.parseBoolean(getProperty("zeppelin.spark.printREPLOutput")); } @@ -530,8 +512,8 @@ public void open() { if (getProperty("master").equals("yarn-client")) { System.setProperty("SPARK_YARN_MODE", "true"); } - if (getProperty().contains("spark.yarn.keytab") && - getProperty().contains("spark.yarn.principal")) { + if (getProperty().containsKey("spark.yarn.keytab") && + getProperty().containsKey("spark.yarn.principal")) { try { String keytab = getProperty().getProperty("spark.yarn.keytab"); String principal = getProperty().getProperty("spark.yarn.principal"); @@ -595,7 +577,11 @@ public void open() { argList.add("-Yrepl-class-based"); argList.add("-Yrepl-outdir"); argList.add(outputDir.getAbsolutePath()); - + if (conf.contains("spark.jars")) { + String jars = StringUtils.join(conf.get("spark.jars").split(","), File.separator); + argList.add("-classpath"); + argList.add(jars); + } scala.collection.immutable.List list = JavaConversions.asScalaBuffer(argList).toList(); @@ -720,11 +706,25 @@ public void open() { logger.error(e.getMessage(), e); } } + } + if (Utils.findClass("org.apache.spark.repl.SparkJLineCompletion", true) != null) { completer = Utils.instantiateClass( "org.apache.spark.repl.SparkJLineCompletion", new Class[]{Utils.findClass("org.apache.spark.repl.SparkIMain")}, new Object[]{intp}); + } else if (Utils.findClass( + "scala.tools.nsc.interpreter.PresentationCompilerCompleter", true) != null) { + completer = Utils.instantiateClass( + "scala.tools.nsc.interpreter.PresentationCompilerCompleter", + new Class[]{ IMain.class }, + new Object[]{ intp }); + } else if (Utils.findClass( + "scala.tools.nsc.interpreter.JLineCompletion", true) != null) { + completer = Utils.instantiateClass( + "scala.tools.nsc.interpreter.JLineCompletion", + new Class[]{ IMain.class }, + new Object[]{ intp }); } if (Utils.isSpark2()) { @@ -903,6 +903,11 @@ private List classPath(ClassLoader cl) { @Override public List completion(String buf, int cursor) { + if (completer == null) { + logger.warn("Can't find completer"); + return new LinkedList(); + } + if (buf.length() < cursor) { cursor = buf.length(); } @@ -911,22 +916,18 @@ public List completion(String buf, int cursor) { completionText = ""; cursor = completionText.length(); } - if (Utils.isScala2_10()) { - ScalaCompleter c = (ScalaCompleter) Utils.invokeMethod(completer, "completer"); - Candidates ret = c.complete(completionText, cursor); - List candidates = WrapAsJava$.MODULE$.seqAsJavaList(ret.candidates()); - List completions = new LinkedList(); + ScalaCompleter c = (ScalaCompleter) Utils.invokeMethod(completer, "completer"); + Candidates ret = c.complete(completionText, cursor); - for (String candidate : candidates) { - completions.add(new InterpreterCompletion(candidate, candidate)); - } + List candidates = WrapAsJava$.MODULE$.seqAsJavaList(ret.candidates()); + List completions = new LinkedList(); - return completions; - } else { - return new LinkedList(); + for (String candidate : candidates) { + completions.add(new InterpreterCompletion(candidate, candidate)); } + return completions; } private String getCompletionTargetString(String text, int cursor) { diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkOutputStream.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkOutputStream.java index 98a4090b117..e454994aa0d 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkOutputStream.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkOutputStream.java @@ -17,17 +17,20 @@ package org.apache.zeppelin.spark; import org.apache.zeppelin.interpreter.InterpreterOutput; +import org.slf4j.Logger; import java.io.IOException; -import java.io.OutputStream; /** * InterpreterOutput can be attached / detached. */ -public class SparkOutputStream extends OutputStream { +public class SparkOutputStream extends LogOutputStream { + + public static Logger logger; InterpreterOutput interpreterOutput; - public SparkOutputStream() { + public SparkOutputStream(Logger logger) { + this.logger = logger; } public InterpreterOutput getInterpreterOutput() { @@ -40,6 +43,7 @@ public void setInterpreterOutput(InterpreterOutput interpreterOutput) { @Override public void write(int b) throws IOException { + super.write(b); if (interpreterOutput != null) { interpreterOutput.write(b); } @@ -47,6 +51,7 @@ public void write(int b) throws IOException { @Override public void write(byte [] b) throws IOException { + super.write(b); if (interpreterOutput != null) { interpreterOutput.write(b); } @@ -54,13 +59,20 @@ public void write(byte [] b) throws IOException { @Override public void write(byte [] b, int offset, int len) throws IOException { + super.write(b, offset, len); if (interpreterOutput != null) { interpreterOutput.write(b, offset, len); } } + @Override + protected void processLine(String s, int i) { + logger.debug("Interpreter output:" + s); + } + @Override public void close() throws IOException { + super.close(); if (interpreterOutput != null) { interpreterOutput.close(); } @@ -68,6 +80,7 @@ public void close() throws IOException { @Override public void flush() throws IOException { + super.flush(); if (interpreterOutput != null) { interpreterOutput.flush(); } diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java index 8329641d8f3..06139496020 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.spark.SparkContext; import org.apache.spark.SparkRBackend; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; @@ -70,11 +71,16 @@ public void open() { int port = SparkRBackend.port(); SparkInterpreter sparkInterpreter = getSparkInterpreter(); - ZeppelinRContext.setSparkContext(sparkInterpreter.getSparkContext()); + SparkContext sc = sparkInterpreter.getSparkContext(); + SparkVersion sparkVersion = new SparkVersion(sc.version()); + ZeppelinRContext.setSparkContext(sc); + if (Utils.isSpark2()) { + ZeppelinRContext.setSparkSession(sparkInterpreter.getSparkSession()); + } ZeppelinRContext.setSqlContext(sparkInterpreter.getSQLContext()); - ZeppelinRContext.setZepplinContext(sparkInterpreter.getZeppelinContext()); + ZeppelinRContext.setZeppelinContext(sparkInterpreter.getZeppelinContext()); - zeppelinR = new ZeppelinR(rCmdPath, sparkRLibPath, port); + zeppelinR = new ZeppelinR(rCmdPath, sparkRLibPath, port, sparkVersion); try { zeppelinR.open(); } catch (IOException e) { diff --git a/spark/src/main/java/org/apache/zeppelin/spark/Utils.java b/spark/src/main/java/org/apache/zeppelin/spark/Utils.java index 328fa199ca6..78304fd8713 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/Utils.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/Utils.java @@ -56,10 +56,16 @@ static Object invokeStaticMethod(Class c, String name) { } static Class findClass(String name) { + return findClass(name, false); + } + + static Class findClass(String name, boolean silence) { try { return Utils.class.forName(name); } catch (ClassNotFoundException e) { - logger.error(e.getMessage(), e); + if (!silence) { + logger.error(e.getMessage(), e); + } return null; } } @@ -83,6 +89,8 @@ static boolean isScala2_10() { return true; } catch (ClassNotFoundException e) { return false; + } catch (IncompatibleClassChangeError e) { + return false; } } diff --git a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinR.java b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinR.java index 0ff07403911..26488337b11 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinR.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinR.java @@ -36,6 +36,7 @@ public class ZeppelinR implements ExecuteResultHandler { Logger logger = LoggerFactory.getLogger(ZeppelinR.class); private final String rCmdPath; + private final SparkVersion sparkVersion; private DefaultExecutor executor; private SparkOutputStream outputStream; private PipedOutputStream input; @@ -107,9 +108,11 @@ public Object getValue() { * @param rCmdPath R repl commandline path * @param libPath sparkr library path */ - public ZeppelinR(String rCmdPath, String libPath, int sparkRBackendPort) { + public ZeppelinR(String rCmdPath, String libPath, int sparkRBackendPort, + SparkVersion sparkVersion) { this.rCmdPath = rCmdPath; this.libPath = libPath; + this.sparkVersion = sparkVersion; this.port = sparkRBackendPort; try { File scriptFile = File.createTempFile("zeppelin_sparkr-", ".R"); @@ -137,9 +140,10 @@ public void open() throws IOException { cmd.addArgument(Integer.toString(hashCode())); cmd.addArgument(Integer.toString(port)); cmd.addArgument(libPath); + cmd.addArgument(Integer.toString(sparkVersion.toNumber())); executor = new DefaultExecutor(); - outputStream = new SparkOutputStream(); + outputStream = new SparkOutputStream(logger); input = new PipedOutputStream(); PipedInputStream in = new PipedInputStream(input); diff --git a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinRContext.java b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinRContext.java index 82c320d7f21..935410bdd59 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinRContext.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/ZeppelinRContext.java @@ -27,12 +27,13 @@ public class ZeppelinRContext { private static SparkContext sparkContext; private static SQLContext sqlContext; private static ZeppelinContext zeppelinContext; + private static Object sparkSession; public static void setSparkContext(SparkContext sparkContext) { ZeppelinRContext.sparkContext = sparkContext; } - public static void setZepplinContext(ZeppelinContext zeppelinContext) { + public static void setZeppelinContext(ZeppelinContext zeppelinContext) { ZeppelinRContext.zeppelinContext = zeppelinContext; } @@ -40,6 +41,10 @@ public static void setSqlContext(SQLContext sqlContext) { ZeppelinRContext.sqlContext = sqlContext; } + public static void setSparkSession(Object sparkSession) { + ZeppelinRContext.sparkSession = sparkSession; + } + public static SparkContext getSparkContext() { return sparkContext; } @@ -52,4 +57,7 @@ public static ZeppelinContext getZeppelinContext() { return zeppelinContext; } + public static Object getSparkSession() { + return sparkSession; + } } diff --git a/spark/src/main/resources/R/zeppelin_sparkr.R b/spark/src/main/resources/R/zeppelin_sparkr.R index fe2a16b973f..d9517749bbf 100644 --- a/spark/src/main/resources/R/zeppelin_sparkr.R +++ b/spark/src/main/resources/R/zeppelin_sparkr.R @@ -21,6 +21,7 @@ args <- commandArgs(trailingOnly = TRUE) hashCode <- as.integer(args[1]) port <- as.integer(args[2]) libPath <- args[3] +version <- as.integer(args[4]) rm(args) print(paste("Port ", toString(port))) @@ -41,6 +42,10 @@ assign(".scStartTime", as.integer(Sys.time()), envir = SparkR:::.sparkREnv) # setup spark env assign(".sc", SparkR:::callJStatic("org.apache.zeppelin.spark.ZeppelinRContext", "getSparkContext"), envir = SparkR:::.sparkREnv) assign("sc", get(".sc", envir = SparkR:::.sparkREnv), envir=.GlobalEnv) +if (version >= 200) { + assign(".sparkRsession", SparkR:::callJStatic("org.apache.zeppelin.spark.ZeppelinRContext", "getSparkSession"), envir = SparkR:::.sparkREnv) + assign("spark", get(".sparkRsession", envir = SparkR:::.sparkREnv), envir = .GlobalEnv) +} assign(".sqlc", SparkR:::callJStatic("org.apache.zeppelin.spark.ZeppelinRContext", "getSqlContext"), envir = SparkR:::.sparkREnv) assign("sqlContext", get(".sqlc", envir = SparkR:::.sparkREnv), envir = .GlobalEnv) assign(".zeppelinContext", SparkR:::callJStatic("org.apache.zeppelin.spark.ZeppelinRContext", "getZeppelinContext"), envir = .GlobalEnv) diff --git a/spark/src/main/resources/interpreter-setting.json b/spark/src/main/resources/interpreter-setting.json index 2343a0f9745..d87a6c7ff75 100644 --- a/spark/src/main/resources/interpreter-setting.json +++ b/spark/src/main/resources/interpreter-setting.json @@ -46,7 +46,7 @@ "envName": "ZEPPELIN_SPARK_MAXRESULT", "propertyName": "zeppelin.spark.maxResult", "defaultValue": "1000", - "description": "Max number of SparkSQL result to display." + "description": "Max number of Spark SQL result to display." }, "master": { "envName": "MASTER", @@ -77,7 +77,7 @@ "envName": "ZEPPELIN_SPARK_MAXRESULT", "propertyName": "zeppelin.spark.maxResult", "defaultValue": "1000", - "description": "Max number of SparkSQL result to display." + "description": "Max number of Spark SQL result to display." }, "zeppelin.spark.importImplicit": { "envName": "ZEPPELIN_SPARK_IMPORTIMPLICIT", diff --git a/spark/src/main/resources/python/zeppelin_pyspark.py b/spark/src/main/resources/python/zeppelin_pyspark.py index 0380afa7fea..9a405566036 100644 --- a/spark/src/main/resources/python/zeppelin_pyspark.py +++ b/spark/src/main/resources/python/zeppelin_pyspark.py @@ -27,19 +27,21 @@ from pyspark.accumulators import Accumulator, AccumulatorParam from pyspark.broadcast import Broadcast from pyspark.serializers import MarshalSerializer, PickleSerializer +import ast +import traceback # for back compatibility from pyspark.sql import SQLContext, HiveContext, Row class Logger(object): def __init__(self): - self.out = "" + pass def write(self, message): intp.appendOutput(message) def reset(self): - self.out = "" + pass def flush(self): pass @@ -230,7 +232,7 @@ def getCompletion(self, text_value): try: stmts = req.statements().split("\n") jobGroup = req.jobGroup() - final_code = None + final_code = [] for s in stmts: if s == None: @@ -241,15 +243,27 @@ def getCompletion(self, text_value): if len(s_stripped) == 0 or s_stripped.startswith("#"): continue - if final_code: - final_code += "\n" + s - else: - final_code = s + final_code.append(s) if final_code: - compiledCode = compile(final_code, "", "exec") + # use exec mode to compile the statements except the last statement, + # so that the last statement's evaluation will be printed to stdout sc.setJobGroup(jobGroup, "Zeppelin") - eval(compiledCode) + code = compile('\n'.join(final_code), '', 'exec', ast.PyCF_ONLY_AST, 1) + to_run_exec, to_run_single = code.body[:-1], code.body[-1:] + + try: + for node in to_run_exec: + mod = ast.Module([node]) + code = compile(mod, '', 'exec') + exec(code) + + for node in to_run_single: + mod = ast.Interactive([node]) + code = compile(mod, '', 'single') + exec(code) + except: + raise Exception(traceback.format_exc()) intp.setStatementsFinished("", False) except Py4JJavaError: diff --git a/spark/src/main/sparkr-resources/interpreter-setting.json b/spark/src/main/sparkr-resources/interpreter-setting.json index 4902baf9f76..f884fe48702 100644 --- a/spark/src/main/sparkr-resources/interpreter-setting.json +++ b/spark/src/main/sparkr-resources/interpreter-setting.json @@ -46,7 +46,7 @@ "envName": "ZEPPELIN_SPARK_MAXRESULT", "propertyName": "zeppelin.spark.maxResult", "defaultValue": "1000", - "description": "Max number of SparkSQL result to display." + "description": "Max number of Spark SQL result to display." }, "master": { "envName": "MASTER", @@ -77,7 +77,7 @@ "envName": "ZEPPELIN_SPARK_MAXRESULT", "propertyName": "zeppelin.spark.maxResult", "defaultValue": "1000", - "description": "Max number of SparkSQL result to display." + "description": "Max number of Spark SQL result to display." }, "zeppelin.spark.importImplicit": { "envName": "ZEPPELIN_SPARK_IMPORTIMPLICIT", diff --git a/spark/src/test/java/org/apache/zeppelin/spark/SparkInterpreterTest.java b/spark/src/test/java/org/apache/zeppelin/spark/SparkInterpreterTest.java index badc4e20b0d..1c7979fc428 100644 --- a/spark/src/test/java/org/apache/zeppelin/spark/SparkInterpreterTest.java +++ b/spark/src/test/java/org/apache/zeppelin/spark/SparkInterpreterTest.java @@ -19,16 +19,16 @@ import static org.junit.Assert.*; -import java.io.BufferedReader; import java.io.File; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Properties; import org.apache.spark.SparkConf; import org.apache.spark.SparkContext; -import org.apache.spark.repl.SparkILoop; import org.apache.zeppelin.display.AngularObjectRegistry; +import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import org.apache.zeppelin.resource.LocalResourcePool; import org.apache.zeppelin.resource.WellKnownResourceName; import org.apache.zeppelin.user.AuthenticationInfo; @@ -42,7 +42,6 @@ import org.junit.runners.MethodSorters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import scala.tools.nsc.interpreter.IMain; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class SparkInterpreterTest { @@ -282,4 +281,10 @@ public void testDisableImplicitImport() { assertEquals(Code.ERROR, repl2.interpret(ddl, context).code()); repl2.close(); } + + @Test + public void testCompletion() { + List completions = repl.completion("sc.", "sc.".length()); + assertTrue(completions.size() > 0); + } } diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml index 47644eefcbf..d33038e88ed 100644 --- a/zeppelin-display/pom.xml +++ b/zeppelin-display/pom.xml @@ -81,6 +81,7 @@ org.scala-lang scala-library ${scala.version} + provided @@ -94,15 +95,12 @@ scala-2.11 - - scala-2.11 - - org.scala-lang.modules scala-xml_${scala.binary.version} 1.0.2 + provided @@ -110,24 +108,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .git/ - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - **/README.md - - - - maven-failsafe-plugin 2.16 diff --git a/zeppelin-distribution/README.md b/zeppelin-distribution/README.md index 72fd63077ba..b5dd2e07437 100644 --- a/zeppelin-distribution/README.md +++ b/zeppelin-distribution/README.md @@ -15,17 +15,19 @@ limitations under the License. --> -# Distribution archive of Zeppelin project # +# Distribution archive of Apache Zeppelin -Zeppelin is distributed as a single gzip archive with the following structure: +Apache Zeppelin is distributed as a single gzip archive with the following structure: ``` -zeppelin +Zeppelin ├── bin │ ├── zeppelin.sh │ └── seppelin-deamon.sh - ├── lib ├── conf + ├── interpreter + ├── lib + ├── licenses ├── zan-repo │ ├── txt.wordcount │ ├── vis.bubble @@ -33,12 +35,11 @@ zeppelin │ ├── ml.something │ └── ... ├── zeppelin-server-.jar - ├── zeppelin-web-.war - └── zeppelin-cli-.jar + └── zeppelin-web-.war ``` -We use maven-assembly-pugin to build it, see distribution.xml for details +We use `maven-assembly-plugin` to build it, see `zeppelin-distribution/src/assemble/distribution.xml ` for details. -**IMPORTANT:** _/lib_ subdirectory contains all transitive dependencies of the zeppelin-distribution module, -automatically resolved by maven, except for explicitly excluded _server_, _web_ and _cli_ zeppelin sub-modules. +>**IMPORTANT:** `_/lib_` subdirectory contains all transitive dependencies of the `zeppelin-distribution` module, +automatically resolved by maven, except for explicitly excluded `_server_` and `_web_` Zeppelin sub-modules. diff --git a/zeppelin-distribution/build-infrastructure.md b/zeppelin-distribution/build-infrastructure.md index b8b3e2088d6..676266c36f9 100644 --- a/zeppelin-distribution/build-infrastructure.md +++ b/zeppelin-distribution/build-infrastructure.md @@ -15,38 +15,41 @@ limitations under the License. --> -Zeppelin dependency graph: --------------- - hive, hadoop, ... +# Apache Zeppelin Build Infrastructure + +## Dependency graph + +``` + e.g. hive, hadoop, ... | | | v v v - Zeppelin Server <- Zengine -> Zeppelin CLI + Zeppelin Server <- Zengine + | zeppeli web v ZAN +``` + + +## Artifacts + - Zeppelin Server : Web UI, server to host it / executable + - Zeppelin Web : Web UI, clint-side JS app / HTML+JavaScript; war + - Zeppelin Zengine : Main library / java library + - ZAN -Zeppelin artifacts: ------------------- -Zeppelin CLI - Commandline UI - executable -Zeppelin Server - Web UI, server to host it - executable -Zwppwlin Web - Web UI, clint-side JS app - HTML+JavaScript; war -Zengine - Main library - java library -ZAN - +## Build process + - compile => *.class, minify *.js + - build modules => *.jar, war + - test => UnitTest reports + - package -P build-distr => final .zip + - integration-test => selenium over running zeppelin-server (from package) -Build process: -------------- -compile => *.class, minify *.js -build modules => *.jar, war -test => UnitTest reports -package -P build-distr => final .zip -integration-test => selenium over running zeppelin-server (from package) +## Verify -verify: - pre-inegration-test => start Zeppelin - integration-test - post-inegration-test => stop Zeppelin + - pre-inegration-test => start Zeppelin + - integration-test + - post-inegration-test => stop Zeppelin diff --git a/zeppelin-distribution/pom.xml b/zeppelin-distribution/pom.xml index f65044e6faf..378c980909a 100644 --- a/zeppelin-distribution/pom.xml +++ b/zeppelin-distribution/pom.xml @@ -114,10 +114,6 @@ scala-2.11 - - scala-2.11 - - diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE index ffecbe21ec0..b33ad64b108 100644 --- a/zeppelin-distribution/src/bin_license/LICENSE +++ b/zeppelin-distribution/src/bin_license/LICENSE @@ -114,6 +114,7 @@ The following components are provided under Apache License. (Apache 2.0) Utility classes for Jetty (org.mortbay.jetty:jetty-util:6.1.26 - http://javadox.com/org.mortbay.jetty/jetty/6.1.26/overview-tree.html) (Apache 2.0) Servlet API (org.mortbay.jetty:servlet-api:2.5-20081211 - https://en.wikipedia.org/wiki/Jetty_(web_server)) (Apache 2.0) Google HTTP Client Library for Java (com.google.http-client:google-http-client-jackson2:1.21.0 - https://github.com/google/google-http-java-client/tree/dev/google-http-client-jackson2) + (Apache 2.0) angular-esri-map (https://github.com/Esri/angular-esri-map) ======================================================================== MIT licenses diff --git a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml index f6a73385931..6305a2e61ed 100644 --- a/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml +++ b/zeppelin-examples/zeppelin-example-horizontalbar/pom.xml @@ -105,16 +105,6 @@ - - - org.apache.rat - apache-rat-plugin - - - **/horizontalbar_mockdata.txt - - - diff --git a/zeppelin-interpreter/pom.xml b/zeppelin-interpreter/pom.xml index 3309746b527..9a509004224 100644 --- a/zeppelin-interpreter/pom.xml +++ b/zeppelin-interpreter/pom.xml @@ -217,24 +217,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - *.md - dependency-reduced-pom.xml - - - - org.apache.maven.plugins maven-shade-plugin diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java index 07f9cbabcad..42caafdfc61 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java @@ -345,10 +345,14 @@ public static void register(String name, String group, String className, } public static void register(RegisteredInterpreter registeredInterpreter) { - // TODO(jongyoul): Error should occur when two same interpreter key with different settings String interpreterKey = registeredInterpreter.getInterpreterKey(); if (!registeredInterpreters.containsKey(interpreterKey)) { registeredInterpreters.put(interpreterKey, registeredInterpreter); + } else { + RegisteredInterpreter existInterpreter = registeredInterpreters.get(interpreterKey); + if (!existInterpreter.getProperties().equals(registeredInterpreter.getProperties())) { + logger.error("exist registeredInterpreter with the same key but has different settings."); + } } } diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterProperty.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterProperty.java index 488f2a1fa93..c69de5d4a20 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterProperty.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterProperty.java @@ -70,8 +70,15 @@ public void setDescription(String description) { this.description = description; } + public int hashCode() { + return this.toString().hashCode(); + } + + public boolean equals(Object o) { + return this.toString().equals(o.toString()); + } + public String getValue() { - //TODO(jongyoul): Remove SparkInterpreter's getSystemDefault method if (envName != null && !envName.isEmpty()) { String envValue = System.getenv().get(envName); if (envValue != null) { @@ -90,7 +97,7 @@ public String getValue() { @Override public String toString() { - return String.format("{envName=%s, propertyName=%s, defaultValue=%s, description=%20s", envName, - propertyName, defaultValue, description); + return String.format("{envName=%s, propertyName=%s, defaultValue=%s, description=%20s}", + envName, propertyName, defaultValue, description); } } diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterPropertyBuilder.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterPropertyBuilder.java index f077b4e45e0..f33dc7c37e1 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterPropertyBuilder.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterPropertyBuilder.java @@ -31,6 +31,13 @@ public InterpreterPropertyBuilder add(String name, String defaultValue, String d return this; } + public InterpreterPropertyBuilder add(String name, String envName, String propertyName, + String defaultValue, String description){ + properties.put(name, + new InterpreterProperty(envName, propertyName, defaultValue, description)); + return this; + } + public Map build(){ return properties; } diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputBuffer.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputBuffer.java new file mode 100644 index 00000000000..e1484dabaaa --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputBuffer.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zeppelin.interpreter.remote; + +/** + * This element stores the buffered + * append-data of paragraph's output. + */ +public class AppendOutputBuffer { + + private String noteId; + private String paragraphId; + private String data; + + public AppendOutputBuffer(String noteId, String paragraphId, String data) { + this.noteId = noteId; + this.paragraphId = paragraphId; + this.data = data; + } + + public String getNoteId() { + return noteId; + } + + public String getParagraphId() { + return paragraphId; + } + + public String getData() { + return data; + } + +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunner.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunner.java new file mode 100644 index 00000000000..86ea11a8bda --- /dev/null +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunner.java @@ -0,0 +1,118 @@ +/* + * 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.interpreter.remote; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This thread sends paragraph's append-data + * periodically, rather than continously, with + * a period of BUFFER_TIME_MS. It handles append-data + * for all paragraphs across all notebooks. + */ +public class AppendOutputRunner implements Runnable { + + private static final Logger logger = + LoggerFactory.getLogger(AppendOutputRunner.class); + public static final Long BUFFER_TIME_MS = new Long(100); + private static final Long SAFE_PROCESSING_TIME = new Long(10); + private static final Long SAFE_PROCESSING_STRING_SIZE = new Long(100000); + + private final BlockingQueue queue = new LinkedBlockingQueue<>(); + private final RemoteInterpreterProcessListener listener; + + public AppendOutputRunner(RemoteInterpreterProcessListener listener) { + this.listener = listener; + } + + @Override + public void run() { + + Map > noteMap = new HashMap<>(); + List list = new LinkedList<>(); + + /* "drainTo" method does not wait for any element + * to be present in the queue, and thus this loop would + * continuosly run (with period of BUFFER_TIME_MS). "take()" method + * waits for the queue to become non-empty and then removes + * one element from it. Rest elements from queue (if present) are + * removed using "drainTo" method. Thus we save on some un-necessary + * cpu-cycles. + */ + try { + list.add(queue.take()); + } catch (InterruptedException e) { + logger.error("Wait for OutputBuffer queue interrupted: " + e.getMessage()); + } + Long processingStartTime = System.currentTimeMillis(); + queue.drainTo(list); + + for (AppendOutputBuffer buffer: list) { + String noteId = buffer.getNoteId(); + String paragraphId = buffer.getParagraphId(); + + Map paragraphMap = (noteMap.containsKey(noteId)) ? + noteMap.get(noteId) : new HashMap(); + StringBuilder builder = paragraphMap.containsKey(paragraphId) ? + paragraphMap.get(paragraphId) : new StringBuilder(); + + builder.append(buffer.getData()); + paragraphMap.put(paragraphId, builder); + noteMap.put(noteId, paragraphMap); + } + Long processingTime = System.currentTimeMillis() - processingStartTime; + + if (processingTime > SAFE_PROCESSING_TIME) { + logger.warn("Processing time for buffered append-output is high: " + + processingTime + " milliseconds."); + } else { + logger.debug("Processing time for append-output took " + + processingTime + " milliseconds"); + } + + Long sizeProcessed = new Long(0); + for (String noteId: noteMap.keySet()) { + for (String paragraphId: noteMap.get(noteId).keySet()) { + String data = noteMap.get(noteId).get(paragraphId).toString(); + sizeProcessed += data.length(); + listener.onOutputAppend(noteId, paragraphId, data); + } + } + + if (sizeProcessed > SAFE_PROCESSING_STRING_SIZE) { + logger.warn("Processing size for buffered append-output is high: " + + sizeProcessed + " characters."); + } else { + logger.debug("Processing size for append-output is " + + sizeProcessed + " characters"); + } + } + + public void appendBuffer(String noteId, String paragraphId, String outputToAppend) { + queue.offer(new AppendOutputBuffer(noteId, paragraphId, outputToAppend)); + } + +} diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java index db740f4873a..3e6fa3943ed 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreter.java @@ -295,7 +295,9 @@ public void close() { @Override public InterpreterResult interpret(String st, InterpreterContext context) { - logger.debug("st: {}", st); + if (logger.isDebugEnabled()) { + logger.debug("st:\n{}", st); + } FormType form = getFormType(); RemoteInterpreterProcess interpreterProcess = getInterpreterProcess(); Client client = null; diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java index 48c14d50bde..090aeeaaee3 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java @@ -39,12 +39,18 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** * Processes message from RemoteInterpreter process */ public class RemoteInterpreterEventPoller extends Thread { private static final Logger logger = LoggerFactory.getLogger(RemoteInterpreterEventPoller.class); + private static final ScheduledExecutorService appendService = + Executors.newSingleThreadScheduledExecutor(); private final RemoteInterpreterProcessListener listener; private final ApplicationEventListener appListener; @@ -72,6 +78,9 @@ public void setInterpreterGroup(InterpreterGroup interpreterGroup) { @Override public void run() { Client client = null; + AppendOutputRunner runner = new AppendOutputRunner(listener); + ScheduledFuture appendFuture = appendService.scheduleWithFixedDelay( + runner, 0, AppendOutputRunner.BUFFER_TIME_MS, TimeUnit.MILLISECONDS); while (!shutdown) { // wait and retry @@ -157,7 +166,7 @@ public void run() { String appId = outputAppend.get("appId"); if (appId == null) { - listener.onOutputAppend(noteId, paragraphId, outputToAppend); + runner.appendBuffer(noteId, paragraphId, outputToAppend); } else { appListener.onOutputAppend(noteId, paragraphId, appId, outputToAppend); } @@ -192,6 +201,9 @@ public void run() { logger.error("Can't handle event " + event, e); } } + if (appendFuture != null) { + appendFuture.cancel(true); + } } private void sendResourcePoolResponseGetAll(ResourceSet resourceSet) { diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java index 173026562f3..7ddb92838f4 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java @@ -34,6 +34,7 @@ import org.apache.zeppelin.helium.*; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.interpreter.dev.ZeppelinDevServer; import org.apache.zeppelin.interpreter.thrift.*; import org.apache.zeppelin.resource.*; import org.apache.zeppelin.scheduler.Job; @@ -133,7 +134,11 @@ public boolean isRunning() { public static void main(String[] args) throws TTransportException, InterruptedException { - int port = Integer.parseInt(args[0]); + + int port = ZeppelinDevServer.DEFAULT_TEST_INTERPRETER_PORT; + if (args.length > 0) { + port = Integer.parseInt(args[0]); + } RemoteInterpreterServer remoteInterpreterServer = new RemoteInterpreterServer(port); remoteInterpreterServer.start(); remoteInterpreterServer.join(); @@ -280,7 +285,9 @@ public void close(String noteId, String className) throws TException { @Override public RemoteInterpreterResult interpret(String noteId, String className, String st, RemoteInterpreterContext interpreterContext) throws TException { - logger.debug("st: {}", st); + if (logger.isDebugEnabled()) { + logger.debug("st:\n{}", st); + } Interpreter intp = getInterpreter(noteId, className); InterpreterContext context = convert(interpreterContext); diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunnerTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunnerTest.java new file mode 100644 index 00000000000..8e9f5b361e6 --- /dev/null +++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/AppendOutputRunnerTest.java @@ -0,0 +1,235 @@ +/* + * 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.interpreter.remote; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.junit.After; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class AppendOutputRunnerTest { + + private static final int NUM_EVENTS = 10000; + private static final int NUM_CLUBBED_EVENTS = 100; + private static final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture future = null; + /* It is being accessed by multiple threads. + * While loop for 'loopForBufferCompletion' could + * run for-ever. + */ + private volatile static int numInvocations = 0; + + @After + public void afterEach() { + if (future != null) { + future.cancel(true); + } + } + + @Test + public void testSingleEvent() throws InterruptedException { + RemoteInterpreterProcessListener listener = mock(RemoteInterpreterProcessListener.class); + String[][] buffer = {{"note", "para", "data\n"}}; + + loopForCompletingEvents(listener, 1, buffer); + verify(listener, times(1)).onOutputAppend(any(String.class), any(String.class), any(String.class)); + verify(listener, times(1)).onOutputAppend("note", "para", "data\n"); + } + + @Test + public void testMultipleEventsOfSameParagraph() throws InterruptedException { + RemoteInterpreterProcessListener listener = mock(RemoteInterpreterProcessListener.class); + String note1 = "note1"; + String para1 = "para1"; + String[][] buffer = { + {note1, para1, "data1\n"}, + {note1, para1, "data2\n"}, + {note1, para1, "data3\n"} + }; + + loopForCompletingEvents(listener, 1, buffer); + verify(listener, times(1)).onOutputAppend(any(String.class), any(String.class), any(String.class)); + verify(listener, times(1)).onOutputAppend(note1, para1, "data1\ndata2\ndata3\n"); + } + + @Test + public void testMultipleEventsOfDifferentParagraphs() throws InterruptedException { + RemoteInterpreterProcessListener listener = mock(RemoteInterpreterProcessListener.class); + String note1 = "note1"; + String note2 = "note2"; + String para1 = "para1"; + String para2 = "para2"; + String[][] buffer = { + {note1, para1, "data1\n"}, + {note1, para2, "data2\n"}, + {note2, para1, "data3\n"}, + {note2, para2, "data4\n"} + }; + loopForCompletingEvents(listener, 4, buffer); + + verify(listener, times(4)).onOutputAppend(any(String.class), any(String.class), any(String.class)); + verify(listener, times(1)).onOutputAppend(note1, para1, "data1\n"); + verify(listener, times(1)).onOutputAppend(note1, para2, "data2\n"); + verify(listener, times(1)).onOutputAppend(note2, para1, "data3\n"); + verify(listener, times(1)).onOutputAppend(note2, para2, "data4\n"); + } + + @Test + public void testClubbedData() throws InterruptedException { + RemoteInterpreterProcessListener listener = mock(RemoteInterpreterProcessListener.class); + AppendOutputRunner runner = new AppendOutputRunner(listener); + future = service.scheduleWithFixedDelay(runner, 0, + AppendOutputRunner.BUFFER_TIME_MS, TimeUnit.MILLISECONDS); + Thread thread = new Thread(new BombardEvents(runner)); + thread.start(); + thread.join(); + Thread.sleep(1000); + + /* NUM_CLUBBED_EVENTS is a heuristic number. + * It has been observed that for 10,000 continuos event + * calls, 30-40 Web-socket calls are made. Keeping + * the unit-test to a pessimistic 100 web-socket calls. + */ + verify(listener, atMost(NUM_CLUBBED_EVENTS)).onOutputAppend(any(String.class), any(String.class), any(String.class)); + } + + @Test + public void testWarnLoggerForLargeData() throws InterruptedException { + RemoteInterpreterProcessListener listener = mock(RemoteInterpreterProcessListener.class); + AppendOutputRunner runner = new AppendOutputRunner(listener); + String data = "data\n"; + int numEvents = 100000; + + for (int i=0; i log; + + int warnLogCounter; + LoggingEvent sizeWarnLogEntry = null; + do { + warnLogCounter = 0; + log = appender.getLog(); + for (LoggingEvent logEntry: log) { + if (Level.WARN.equals(logEntry.getLevel())) { + sizeWarnLogEntry = logEntry; + warnLogCounter += 1; + } + } + } while(warnLogCounter != 2); + + String loggerString = "Processing size for buffered append-output is high: " + + (data.length() * numEvents) + " characters."; + assertTrue(loggerString.equals(sizeWarnLogEntry.getMessage())); + } + + private class BombardEvents implements Runnable { + + private final AppendOutputRunner runner; + + private BombardEvents(AppendOutputRunner runner) { + this.runner = runner; + } + + @Override + public void run() { + String noteId = "noteId"; + String paraId = "paraId"; + for (int i=0; i log = new ArrayList<>(); + + @Override + public boolean requiresLayout() { + return false; + } + + @Override + protected void append(final LoggingEvent loggingEvent) { + log.add(loggingEvent); + } + + @Override + public void close() { + } + + public List getLog() { + return new ArrayList<>(log); + } + } + + private void prepareInvocationCounts(RemoteInterpreterProcessListener listener) { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + numInvocations += 1; + return null; + } + }).when(listener).onOutputAppend(any(String.class), any(String.class), any(String.class)); + } + + private void loopForCompletingEvents(RemoteInterpreterProcessListener listener, + int numTimes, String[][] buffer) { + numInvocations = 0; + prepareInvocationCounts(listener); + AppendOutputRunner runner = new AppendOutputRunner(listener); + for (String[] bufferElement: buffer) { + runner.appendBuffer(bufferElement[0], bufferElement[1], bufferElement[2]); + } + future = service.scheduleWithFixedDelay(runner, 0, + AppendOutputRunner.BUFFER_TIME_MS, TimeUnit.MILLISECONDS); + long startTimeMs = System.currentTimeMillis(); + while(numInvocations != numTimes) { + if (System.currentTimeMillis() - startTimeMs > 2000) { + fail("Buffered events were not sent for 2 seconds"); + } + } + } +} \ No newline at end of file diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 76fb72b8b69..862fc30ce35 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -35,6 +35,7 @@ 2.7.7 4.3.6 + 2.6.0 @@ -205,6 +206,61 @@ commons-collections + + org.apache.hadoop + hadoop-common + ${hadoop-common.version} + + + com.sun.jersey + jersey-core + + + com.sun.jersey + jersey-json + + + com.sun.jersey + jersey-server + + + + + javax.servlet + servlet-api + + + org.apache.avro + avro + + + org.apache.jackrabbit + jackrabbit-webdav + + + io.netty + netty + + + commons-httpclient + commons-httpclient + + + org.apache.zookeeper + zookeeper + + + org.eclipse.jgit + org.eclipse.jgit + + + com.jcraft + jsch + + + + + org.quartz-scheduler quartz @@ -301,27 +357,6 @@ - - org.apache.rat - apache-rat-plugin - - - **/.idea/ - **/*.iml - .git/ - .gitignore - **/.settings/* - **/.classpath - **/.project - **/target/** - **/derby.log - **/metastore_db/ - **/README.md - src/test/java/com/webautomation/* - - - - maven-failsafe-plugin 2.16 @@ -413,10 +448,6 @@ scala-2.11 - - scala-2.11 - - diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java new file mode 100644 index 00000000000..cbe490d8de5 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ZeppelinHubRealm.java @@ -0,0 +1,199 @@ +/* + * 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.realm; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authc.AccountException; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Joiner; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; + +/** + * A {@code Realm} implementation that uses the ZeppelinHub to authenticate users. + * + */ +public class ZeppelinHubRealm extends AuthorizingRealm { + + private static final Logger LOG = LoggerFactory.getLogger(ZeppelinHubRealm.class); + private static final String DEFAULT_ZEPPELINHUB_URL = "https://www.zeppelinhub.com"; + private static final String USER_LOGIN_API_ENDPOINT = "api/v1/users/login"; + private static final String JSON_CONTENT_TYPE = "application/json"; + private static final String UTF_8_ENCODING = "UTF-8"; + private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(); + + private final HttpClient httpClient; + private final Gson gson; + + private String zeppelinhubUrl; + private String name; + + public ZeppelinHubRealm() { + super(); + LOG.debug("Init ZeppelinhubRealm"); + //TODO(anthonyc): think about more setting for this HTTP client. + // eg: if user uses proxy etcetc... + httpClient = new HttpClient(); + gson = new Gson(); + name = getClass().getName() + "_" + INSTANCE_COUNT.getAndIncrement(); + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) + throws AuthenticationException { + UsernamePasswordToken token = (UsernamePasswordToken) authToken; + if (StringUtils.isBlank(token.getUsername())) { + throw new AccountException("Empty usernames are not allowed by this realm."); + } + String loginPayload = createLoginPayload(token.getUsername(), token.getPassword()); + User user = authenticateUser(loginPayload); + LOG.debug("{} successfully login via ZeppelinHub", user.login); + return new SimpleAuthenticationInfo(user.login, token.getPassword(), name); + } + + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + // TODO(xxx): future work will be done here. + return null; + } + + protected void onInit() { + super.onInit(); + } + + /** + * Setter of ZeppelinHub URL, this will be called by Shiro based on zeppelinhubUrl property + * in shiro.ini file.

    + * It will also perform a check of ZeppelinHub url {@link #isZeppelinHubUrlValid}, + * if the url is not valid, the default zeppelinhub url will be used. + * + * @param url + */ + public void setZeppelinhubUrl(String url) { + if (StringUtils.isBlank(url)) { + LOG.warn("Zeppelinhub url is empty, setting up default url {}", DEFAULT_ZEPPELINHUB_URL); + zeppelinhubUrl = DEFAULT_ZEPPELINHUB_URL; + } else { + zeppelinhubUrl = (isZeppelinHubUrlValid(url) ? url : DEFAULT_ZEPPELINHUB_URL); + LOG.info("Setting up Zeppelinhub url to {}", zeppelinhubUrl); + } + } + + /** + * Send to ZeppelinHub a login request based on the request body which is a JSON that contains 2 + * fields "login" and "password". + * + * @param requestBody JSON string of ZeppelinHub payload. + * @return Account object with login, name (if set in ZeppelinHub), and mail. + * @throws AuthenticationException if fail to login. + */ + protected User authenticateUser(String requestBody) { + PutMethod put = new PutMethod(Joiner.on("/").join(zeppelinhubUrl, USER_LOGIN_API_ENDPOINT)); + String responseBody = StringUtils.EMPTY; + try { + put.setRequestEntity(new StringRequestEntity(requestBody, JSON_CONTENT_TYPE, UTF_8_ENCODING)); + int statusCode = httpClient.executeMethod(put); + if (statusCode != HttpStatus.SC_OK) { + LOG.error("Cannot login user, HTTP status code is {} instead on 200 (OK)", statusCode); + put.releaseConnection(); + throw new AuthenticationException("Couldnt login to ZeppelinHub. " + + "Login or password incorrect"); + } + responseBody = put.getResponseBodyAsString(); + put.releaseConnection(); + } catch (IOException e) { + LOG.error("Cannot login user", e); + throw new AuthenticationException(e.getMessage()); + } + + User account = null; + try { + account = gson.fromJson(responseBody, User.class); + } catch (JsonParseException e) { + LOG.error("Cannot deserialize ZeppelinHub response to User instance", e); + throw new AuthenticationException("Cannot login to ZeppelinHub"); + } + return account; + } + + /** + * Create a JSON String that represent login payload.

    + * Payload will look like: + * + * { + * 'login': 'userLogin', + * 'password': 'userpassword' + * } + * + * @param login + * @param pwd + * @return + */ + protected String createLoginPayload(String login, char[] pwd) { + StringBuilder sb = new StringBuilder("{\"login\":\""); + return sb.append(login).append("\", \"password\":\"").append(pwd).append("\"}").toString(); + } + + /** + * Perform a Simple URL check by using URI(url).toURL(). + * If the url is not valid, the try-catch condition will catch the exceptions and return false, + * otherwise true will be returned. + * + * @param url + * @return + */ + protected boolean isZeppelinHubUrlValid(String url) { + boolean valid; + try { + new URI(url).toURL(); + valid = true; + } catch (URISyntaxException | MalformedURLException e) { + LOG.error("Zeppelinhub url is not valid, default ZeppelinHub url will be used.", e); + valid = false; + } + return valid; + } + + /** + * Helper class that will be use to deserialize ZeppelinHub response. + */ + protected class User { + public String login; + public String email; + public String name; + } +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java index 2727fb4a411..f1a895c8bcf 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java @@ -67,6 +67,25 @@ public List getUserList(IniRealm r) { return userList; } + + /*** + * Get user roles from shiro.ini + * @param r + * @return + */ + public List getRolesList(IniRealm r) { + List roleList = new ArrayList<>(); + Map getIniRoles = r.getIni().get("roles"); + if (getIniRoles != null) { + Iterator it = getIniRoles.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + roleList.add(pair.getKey().toString().trim()); + } + } + return roleList; + } + /** * function to extract users from LDAP */ diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java index f77aac0c4d1..6025b52c7f1 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java @@ -90,6 +90,9 @@ public Response newSettings(String message) { try { NewInterpreterSettingRequest request = gson.fromJson(message, NewInterpreterSettingRequest.class); + if (request == null) { + return new JsonResponse<>(Status.BAD_REQUEST).build(); + } Properties p = new Properties(); p.putAll(request.getProperties()); InterpreterSetting interpreterSetting = interpreterFactory diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java index 700fe1a1c2d..17ec74fa39d 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -39,18 +38,20 @@ import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.scheduler.Job; +import org.apache.zeppelin.utils.InterpreterBindingUtils; import org.quartz.CronExpression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zeppelin.annotation.ZeppelinApi; -import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.rest.message.CronRequest; -import org.apache.zeppelin.rest.message.InterpreterSettingListForNoteBind; +import org.apache.zeppelin.types.InterpreterSettingsList; import org.apache.zeppelin.rest.message.NewNotebookRequest; import org.apache.zeppelin.rest.message.NewParagraphRequest; import org.apache.zeppelin.rest.message.RunParagraphWithParametersRequest; @@ -117,8 +118,8 @@ public Response putNotePermissions(@PathParam("noteId") String noteId, String re * TODO(jl): Fixed the type of HashSet * https://issues.apache.org/jira/browse/ZEPPELIN-1162 */ - HashMap permMap = - gson.fromJson(req, new TypeToken>() { + HashMap> permMap = + gson.fromJson(req, new TypeToken>>() { }.getType()); Note note = notebook.getNote(noteId); String principal = SecurityUtils.getPrincipal(); @@ -134,9 +135,9 @@ public Response putNotePermissions(@PathParam("noteId") String noteId, String re ownerPermissionError(userAndRoles, notebookAuthorization.getOwners(noteId))).build(); } - HashSet readers = permMap.get("readers"); - HashSet owners = permMap.get("owners"); - HashSet writers = permMap.get("writers"); + HashSet readers = permMap.get("readers"); + HashSet owners = permMap.get("owners"); + HashSet writers = permMap.get("writers"); // Set readers, if writers and owners is empty -> set to user requesting the change if (readers != null && !readers.isEmpty()) { if (writers.isEmpty()) { @@ -186,29 +187,9 @@ public Response bind(@PathParam("noteId") String noteId, String req) throws IOEx @Path("interpreter/bind/{noteId}") @ZeppelinApi public Response bind(@PathParam("noteId") String noteId) { - List settingList = new LinkedList<>(); - - List selectedSettings = notebook.getBindedInterpreterSettings(noteId); - for (InterpreterSetting setting : selectedSettings) { - settingList.add(new InterpreterSettingListForNoteBind(setting.getId(), setting.getName(), - setting.getInterpreterInfos(), true)); - } - - List availableSettings = notebook.getInterpreterFactory().get(); - for (InterpreterSetting setting : availableSettings) { - boolean selected = false; - for (InterpreterSetting selectedSetting : selectedSettings) { - if (selectedSetting.getId().equals(setting.getId())) { - selected = true; - break; - } - } - - if (!selected) { - settingList.add(new InterpreterSettingListForNoteBind(setting.getId(), setting.getName(), - setting.getInterpreterInfos(), false)); - } - } + List settingList = + InterpreterBindingUtils.getInterpreterBindings(notebook, noteId); + notebookServer.broadcastInterpreterBindings(noteId, settingList); return new JsonResponse<>(Status.OK, "", settingList).build(); } @@ -338,7 +319,10 @@ public Response cloneNote(@PathParam("notebookId") String notebookId, String mes throws IOException, CloneNotSupportedException, IllegalArgumentException { LOG.info("clone notebook by JSON {}", message); NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); - String newNoteName = request.getName(); + String newNoteName = null; + if (request != null) { + newNoteName = request.getName(); + } AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); Note newNote = notebook.cloneNote(notebookId, newNoteName, subject); notebookServer.broadcastNote(newNote); @@ -498,7 +482,14 @@ public Response runNoteJobs(@PathParam("notebookId") String notebookId) return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } - note.runAll(); + try { + note.runAll(); + } catch (Exception ex) { + LOG.error("Exception from run", ex); + return new JsonResponse<>(Status.PRECONDITION_FAILED, + ex.getMessage() + "- Not selected or Invalid Interpreter bind").build(); + } + return new JsonResponse<>(Status.OK).build(); } @@ -550,7 +541,36 @@ public Response getNoteJobStatus(@PathParam("notebookId") String notebookId) } /** - * Run paragraph job REST API + * Get notebook paragraph job status REST API + * + * @param notebookId ID of Notebook + * @param paragraphId ID of Paragraph + * @return JSON with status.OK + * @throws IOException, IllegalArgumentException + */ + @GET + @Path("job/{notebookId}/{paragraphId}") + @ZeppelinApi + public Response getNoteParagraphJobStatus(@PathParam("notebookId") String notebookId, + @PathParam("paragraphId") String paragraphId) + throws IOException, IllegalArgumentException { + LOG.info("get notebook paragraph job status."); + Note note = notebook.getNote(notebookId); + if (note == null) { + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); + } + + Paragraph paragraph = note.getParagraph(paragraphId); + if (paragraph == null) { + return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); + } + + return new JsonResponse<>(Status.OK, null, note.generateSingleParagraphInfo(paragraphId)). + build(); + } + + /** + * Run asynchronously paragraph job REST API * * @param message - JSON with params if user wants to update dynamic form's value * null, empty string, empty json if user doesn't want to update @@ -563,7 +583,7 @@ public Response getNoteJobStatus(@PathParam("notebookId") String notebookId) public Response runParagraph(@PathParam("notebookId") String notebookId, @PathParam("paragraphId") String paragraphId, String message) throws IOException, IllegalArgumentException { - LOG.info("run paragraph job {} {} {}", notebookId, paragraphId, message); + LOG.info("run paragraph job asynchronously {} {} {}", notebookId, paragraphId, message); Note note = notebook.getNote(notebookId); if (note == null) { @@ -576,22 +596,60 @@ public Response runParagraph(@PathParam("notebookId") String notebookId, } // handle params if presented - if (!StringUtils.isEmpty(message)) { - RunParagraphWithParametersRequest request = - gson.fromJson(message, RunParagraphWithParametersRequest.class); - Map paramsForUpdating = request.getParams(); - if (paramsForUpdating != null) { - paragraph.settings.getParams().putAll(paramsForUpdating); - AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); - note.setLastReplName(paragraph.getId()); - note.persist(subject); - } - } + handleParagraphParams(message, note, paragraph); note.run(paragraph.getId()); return new JsonResponse<>(Status.OK).build(); } +/** + * Run synchronously a paragraph REST API + * + * @param noteId - noteId + * @param paragraphId - paragraphId + * @param message - JSON with params if user wants to update dynamic form's value + * null, empty string, empty json if user doesn't want to update + * + * @return JSON with status.OK + * @throws IOException, IllegalArgumentException + */ + @POST + @Path("run/{notebookId}/{paragraphId}") + @ZeppelinApi + public Response runParagraphSynchronously(@PathParam("notebookId") String noteId, + @PathParam("paragraphId") String paragraphId, + String message) throws + IOException, IllegalArgumentException { + LOG.info("run paragraph synchronously {} {} {}", noteId, paragraphId, message); + + Note note = notebook.getNote(noteId); + if (note == null) { + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); + } + + Paragraph paragraph = note.getParagraph(paragraphId); + if (paragraph == null) { + return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); + } + + // handle params if presented + handleParagraphParams(message, note, paragraph); + + if (paragraph.getListener() == null) { + note.initializeJobListenerForParagraph(paragraph); + } + + paragraph.run(); + + final InterpreterResult result = paragraph.getResult(); + + if (result.code() == InterpreterResult.Code.SUCCESS) { + return new JsonResponse<>(Status.OK, result).build(); + } else { + return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, result).build(); + } + } + /** * Stop(delete) paragraph job REST API * @@ -652,7 +710,7 @@ public Response registerCronJob(@PathParam("notebookId") String notebookId, Stri Map config = note.getConfig(); config.put("cron", request.getCronString()); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); return new JsonResponse<>(Status.OK).build(); } @@ -680,7 +738,7 @@ public Response removeCronJob(@PathParam("notebookId") String notebookId) Map config = note.getConfig(); config.put("cron", null); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); return new JsonResponse<>(Status.OK).build(); } @@ -783,4 +841,21 @@ public Response search(@QueryParam("q") String queryTerm) { return new JsonResponse<>(Status.OK, notebooksFound).build(); } + + private void handleParagraphParams(String message, Note note, Paragraph paragraph) + throws IOException { + // handle params if presented + if (!StringUtils.isEmpty(message)) { + RunParagraphWithParametersRequest request = + gson.fromJson(message, RunParagraphWithParametersRequest.class); + Map paramsForUpdating = request.getParams(); + if (paramsForUpdating != null) { + paragraph.settings.getParams().putAll(paramsForUpdating); + AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + note.setLastReplName(paragraph.getId()); + note.persist(subject); + } + } + } + } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java index a079a4460c9..7af52c8c8ba 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java @@ -18,9 +18,9 @@ package org.apache.zeppelin.rest; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.jdbc.JdbcRealm; -import org.apache.shiro.realm.ldap.AbstractLdapRealm; import org.apache.shiro.realm.ldap.JndiLdapRealm; import org.apache.shiro.realm.text.IniRealm; import org.apache.zeppelin.annotation.ZeppelinApi; @@ -98,6 +98,7 @@ public Response ticket() { public Response getUserList(@PathParam("searchText") final String searchText) { List usersList = new ArrayList<>(); + List rolesList = new ArrayList<>(); try { GetUserList getUserListObj = new GetUserList(); Collection realmsList = SecurityUtils.getRealmsList(); @@ -107,6 +108,7 @@ public Response getUserList(@PathParam("searchText") final String searchText) { String name = realm.getName(); if (name.equals("iniRealm")) { usersList.addAll(getUserListObj.getUserList((IniRealm) realm)); + rolesList.addAll(getUserListObj.getRolesList((IniRealm) realm)); } else if (name.equals("ldapRealm")) { usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm, searchText)); } else if (name.equals("activeDirectoryRealm")) { @@ -120,8 +122,10 @@ public Response getUserList(@PathParam("searchText") final String searchText) { } catch (Exception e) { LOG.error("Exception in retrieving Users from realms ", e); } - List autoSuggestList = new ArrayList<>(); + List autoSuggestUserList = new ArrayList<>(); + List autoSuggestRoleList = new ArrayList<>(); Collections.sort(usersList); + Collections.sort(rolesList); Collections.sort(usersList, new Comparator() { @Override public int compare(String o1, String o2) { @@ -134,18 +138,28 @@ public int compare(String o1, String o2) { } }); int maxLength = 0; - for (int i = 0; i < usersList.size(); i++) { - String userLowerCase = usersList.get(i).toLowerCase(); - String searchTextLowerCase = searchText.toLowerCase(); - if (userLowerCase.indexOf(searchTextLowerCase) != -1) { + for (String user : usersList) { + if (StringUtils.containsIgnoreCase(user, searchText)) { + autoSuggestUserList.add(user); maxLength++; - autoSuggestList.add(usersList.get(i)); } if (maxLength == 5) { break; } } - return new JsonResponse<>(Response.Status.OK, "", autoSuggestList).build(); + + for (String role : rolesList) { + if (StringUtils.containsIgnoreCase(role, searchText)) { + autoSuggestRoleList.add(role); + } + } + + Map returnListMap = new HashMap<>(); + returnListMap.put("users", autoSuggestUserList); + returnListMap.put("roles", autoSuggestRoleList); + + + return new JsonResponse<>(Response.Status.OK, "", returnListMap).build(); } } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java index cc868d7a1b4..556f404663c 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java @@ -16,6 +16,10 @@ */ package org.apache.zeppelin.server; +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; @@ -55,6 +59,13 @@ public class ActiveDirectoryGroupRealm extends AbstractLdapRealm { private static final String ROLE_NAMES_DELIMETER = ","; + String KEYSTORE_PASS = "activeDirectoryRealm.systemPassword"; + private String hadoopSecurityCredentialPath; + + public void setHadoopSecurityCredentialPath(String hadoopSecurityCredentialPath) { + this.hadoopSecurityCredentialPath = hadoopSecurityCredentialPath; + } + /*-------------------------------------------- | I N S T A N C E V A R I A B L E S | ============================================*/ @@ -91,13 +102,36 @@ public LdapContextFactory getLdapContextFactory() { defaultFactory.setSearchBase(this.searchBase); defaultFactory.setUrl(this.url); defaultFactory.setSystemUsername(this.systemUsername); - defaultFactory.setSystemPassword(this.systemPassword); + defaultFactory.setSystemPassword(getSystemPassword()); this.ldapContextFactory = defaultFactory; } return this.ldapContextFactory; } + private String getSystemPassword() { + String password = ""; + if (StringUtils.isEmpty(this.hadoopSecurityCredentialPath)) { + password = this.systemPassword; + } else { + try { + Configuration configuration = new Configuration(); + configuration.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, + this.hadoopSecurityCredentialPath); + CredentialProvider provider = + CredentialProviderFactory.getProviders(configuration).get(0); + CredentialProvider.CredentialEntry credEntry = provider.getCredentialEntry( + KEYSTORE_PASS); + if (credEntry != null) { + password = new String(credEntry.getCredential()); + } + } catch (Exception e) { + + } + } + return password; + } + /** * Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for * the specified username. This method binds to the LDAP server using the provided username @@ -293,3 +327,4 @@ protected Collection getRoleNamesForGroups(Collection groupNames } } + diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 0f8ce70fc39..19bfba10b54 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -16,19 +16,12 @@ */ package org.apache.zeppelin.socket; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; - -import javax.servlet.http.HttpServletRequest; - import com.google.common.base.Strings; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; import org.apache.zeppelin.display.AngularObject; @@ -37,15 +30,13 @@ import org.apache.zeppelin.helium.ApplicationEventListener; import org.apache.zeppelin.helium.HeliumPackage; import org.apache.zeppelin.interpreter.InterpreterGroup; -import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; -import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; -import org.apache.zeppelin.user.AuthenticationInfo; import org.apache.zeppelin.interpreter.InterpreterOutput; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterSetting; +import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener; +import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import org.apache.zeppelin.notebook.*; -import org.apache.zeppelin.notebook.repo.NotebookRepo; import org.apache.zeppelin.notebook.repo.NotebookRepo.Revision; import org.apache.zeppelin.notebook.socket.Message; import org.apache.zeppelin.notebook.socket.Message.OP; @@ -53,6 +44,9 @@ import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.server.ZeppelinServer; import org.apache.zeppelin.ticket.TicketContainer; +import org.apache.zeppelin.types.InterpreterSettingsList; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.apache.zeppelin.utils.InterpreterBindingUtils; import org.apache.zeppelin.utils.SecurityUtils; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @@ -60,6 +54,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; + /** * Zeppelin websocket service. */ @@ -131,8 +132,17 @@ public void onMessage(NotebookSocket conn, String msg) { } String ticket = TicketContainer.instance.getTicket(messagereceived.principal); - if (ticket != null && !ticket.equals(messagereceived.ticket)) - throw new Exception("Invalid ticket " + messagereceived.ticket + " != " + ticket); + if (ticket != null && !ticket.equals(messagereceived.ticket)){ + /* not to pollute logs, log instead of exception */ + if (StringUtils.isEmpty(messagereceived.ticket)) { + LOG.debug("{} message: invalid ticket {} != {}", messagereceived.op, + messagereceived.ticket, ticket); + } else { + LOG.warn("{} message: invalid ticket {} != {}", messagereceived.op, + messagereceived.ticket, ticket); + } + return; + } ZeppelinConfiguration conf = ZeppelinConfiguration.create(); boolean allowAnonymous = conf. @@ -226,7 +236,7 @@ public void onMessage(NotebookSocket conn, String msg) { listRevisionHistory(conn, notebook, messagereceived); break; case NOTE_REVISION: - getNoteRevision(conn, notebook, messagereceived); + getNoteByRevision(conn, notebook, messagereceived); break; case LIST_NOTEBOOK_JOBS: unicastNotebookJobInfo(conn, messagereceived); @@ -234,6 +244,12 @@ public void onMessage(NotebookSocket conn, String msg) { case LIST_UPDATE_NOTEBOOK_JOBS: unicastUpdateNotebookJobInfo(conn, messagereceived); break; + case GET_INTERPRETER_BINDINGS: + getInterpreterBindings(conn, messagereceived); + break; + case SAVE_INTERPRETER_BINDINGS: + saveInterpreterBindings(conn, messagereceived); + break; default: break; } @@ -320,7 +336,7 @@ private void broadcastToNoteBindedInterpreter(String interpreterGroupId, List ids = notebook.getInterpreterFactory().getInterpreters(note.getId()); for (String id : ids) { if (id.equals(interpreterGroupId)) { - broadcast(note.id(), m); + broadcast(note.getId(), m); } } } @@ -411,6 +427,29 @@ public void unicastUpdateNotebookJobInfo(NotebookSocket conn, Message fromMessag .put("notebookRunningJobs", response))); } + public void saveInterpreterBindings(NotebookSocket conn, Message fromMessage) { + String noteId = (String) fromMessage.data.get("noteID"); + try { + List settingIdList = gson.fromJson(String.valueOf( + fromMessage.data.get("selectedSettingIds")), new TypeToken>() { + }.getType()); + notebook().bindInterpretersToNote(noteId, settingIdList); + broadcastInterpreterBindings(noteId, + InterpreterBindingUtils.getInterpreterBindings(notebook(), noteId)); + } catch (Exception e) { + LOG.error("Error while saving interpreter bindings", e); + } + } + + public void getInterpreterBindings(NotebookSocket conn, Message fromMessage) + throws IOException { + String noteID = (String) fromMessage.data.get("noteID"); + List settingList = + InterpreterBindingUtils.getInterpreterBindings(notebook(), noteID); + conn.send(serializeMessage(new Message(OP.INTERPRETER_BINDINGS) + .put("interpreterBindings", settingList))); + } + public List> generateNotebooksInfo(boolean needsReload, AuthenticationInfo subject) { @@ -434,11 +473,11 @@ public List> generateNotebooksInfo(boolean needsReload, for (Note note : notes) { Map info = new HashMap<>(); - if (hideHomeScreenNotebookFromList && note.id().equals(homescreenNotebookId)) { + if (hideHomeScreenNotebookFromList && note.getId().equals(homescreenNotebookId)) { continue; } - info.put("id", note.id()); + info.put("id", note.getId()); info.put("name", note.getName()); notesInfo.add(info); } @@ -447,7 +486,13 @@ public List> generateNotebooksInfo(boolean needsReload, } public void broadcastNote(Note note) { - broadcast(note.id(), new Message(OP.NOTE).put("note", note)); + broadcast(note.getId(), new Message(OP.NOTE).put("note", note)); + } + + public void broadcastInterpreterBindings(String noteId, + List settingList) { + broadcast(noteId, new Message(OP.INTERPRETER_BINDINGS) + .put("interpreterBindings", settingList)); } public void broadcastNoteList(AuthenticationInfo subject) { @@ -499,7 +544,7 @@ private void sendNote(NotebookSocket conn, HashSet userAndRoles, Noteboo notebookAuthorization.getReaders(noteId)); return; } - addConnectionToNote(note.id(), conn); + addConnectionToNote(note.getId(), conn); conn.send(serializeMessage(new Message(OP.NOTE).put("note", note))); sendAllAngularObjects(note, conn); } else { @@ -523,7 +568,7 @@ private void sendHomeNote(NotebookSocket conn, HashSet userAndRoles, userAndRoles, notebookAuthorization.getReaders(noteId)); return; } - addConnectionToNote(note.id(), conn); + addConnectionToNote(note.getId(), conn); conn.send(serializeMessage(new Message(OP.NOTE).put("note", note))); sendAllAngularObjects(note, conn); } else { @@ -559,7 +604,7 @@ private void updateNote(NotebookSocket conn, HashSet userAndRoles, note.setName(name); note.setConfig(config); if (cronUpdated) { - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); } AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); @@ -598,7 +643,7 @@ private void createNote(NotebookSocket conn, HashSet userAndRoles, } note.persist(subject); - addConnectionToNote(note.id(), (NotebookSocket) conn); + addConnectionToNote(note.getId(), (NotebookSocket) conn); conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", note))); broadcastNoteList(subject); } @@ -652,7 +697,7 @@ private void updateParagraph(NotebookSocket conn, HashSet userAndRoles, p.setTitle((String) fromMessage.get("title")); p.setText((String) fromMessage.get("paragraph")); note.persist(subject); - broadcast(note.id(), new Message(OP.PARAGRAPH).put("paragraph", p)); + broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p)); } private void cloneNote(NotebookSocket conn, HashSet userAndRoles, @@ -662,7 +707,7 @@ private void cloneNote(NotebookSocket conn, HashSet userAndRoles, String name = (String) fromMessage.get("name"); Note newNote = notebook.cloneNote(noteId, name, new AuthenticationInfo(fromMessage.principal)); AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); - addConnectionToNote(newNote.id(), (NotebookSocket) conn); + addConnectionToNote(newNote.getId(), (NotebookSocket) conn); conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", newNote))); broadcastNoteList(subject); } @@ -765,12 +810,12 @@ private void angularObjectUpdated(NotebookSocket conn, HashSet userAndRo List settings = notebook.getInterpreterFactory() .getInterpreterSettings(note.getId()); for (InterpreterSetting setting : settings) { - if (setting.getInterpreterGroup(note.id()) == null) { + if (setting.getInterpreterGroup(note.getId()) == null) { continue; } - if (interpreterGroupId.equals(setting.getInterpreterGroup(note.id()).getId())) { + if (interpreterGroupId.equals(setting.getInterpreterGroup(note.getId()).getId())) { AngularObjectRegistry angularObjectRegistry = setting - .getInterpreterGroup(note.id()).getAngularObjectRegistry(); + .getInterpreterGroup(note.getId()).getAngularObjectRegistry(); // first trying to get local registry ao = angularObjectRegistry.get(varName, noteId, paragraphId); @@ -807,17 +852,17 @@ private void angularObjectUpdated(NotebookSocket conn, HashSet userAndRo List settings = notebook.getInterpreterFactory() .getInterpreterSettings(note.getId()); for (InterpreterSetting setting : settings) { - if (setting.getInterpreterGroup(n.id()) == null) { + if (setting.getInterpreterGroup(n.getId()) == null) { continue; } - if (interpreterGroupId.equals(setting.getInterpreterGroup(n.id()).getId())) { + if (interpreterGroupId.equals(setting.getInterpreterGroup(n.getId()).getId())) { AngularObjectRegistry angularObjectRegistry = setting - .getInterpreterGroup(n.id()).getAngularObjectRegistry(); + .getInterpreterGroup(n.getId()).getAngularObjectRegistry(); this.broadcastExcept( - n.id(), + n.getId(), new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao) .put("interpreterGroupId", interpreterGroupId) - .put("noteId", n.id()) + .put("noteId", n.getId()) .put("paragraphId", ao.getParagraphId()), conn); } @@ -825,10 +870,10 @@ private void angularObjectUpdated(NotebookSocket conn, HashSet userAndRo } } else { // broadcast to all web session for the note this.broadcastExcept( - note.id(), + note.getId(), new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao) .put("interpreterGroupId", interpreterGroupId) - .put("noteId", note.id()) + .put("noteId", note.getId()) .put("paragraphId", ao.getParagraphId()), conn); } @@ -1104,7 +1149,7 @@ private void runParagraph(NotebookSocket conn, HashSet userAndRoles, Not new InterpreterResult(InterpreterResult.Code.ERROR, ex.getMessage()), ex); p.setStatus(Status.ERROR); - broadcast(note.id(), new Message(OP.PARAGRAPH).put("paragraph", p)); + broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p)); } } } @@ -1136,7 +1181,7 @@ private void checkpointNotebook(NotebookSocket conn, Notebook notebook, AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); Revision revision = notebook.checkpointNote(noteId, commitMessage, subject); if (revision != null) { - List revisions = notebook.listRevisionHistory(noteId, subject); + List revisions = notebook.listRevisionHistory(noteId, subject); conn.send(serializeMessage(new Message(OP.LIST_REVISION_HISTORY) .put("revisionList", revisions))); } @@ -1146,21 +1191,21 @@ private void listRevisionHistory(NotebookSocket conn, Notebook notebook, Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("noteId"); AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); - List revisions = notebook.listRevisionHistory(noteId, subject); + List revisions = notebook.listRevisionHistory(noteId, subject); conn.send(serializeMessage(new Message(OP.LIST_REVISION_HISTORY) .put("revisionList", revisions))); } - private void getNoteRevision(NotebookSocket conn, Notebook notebook, Message fromMessage) + private void getNoteByRevision(NotebookSocket conn, Notebook notebook, Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("noteId"); - Revision revision = (Revision) fromMessage.get("revision"); + String revisionId = (String) fromMessage.get("revisionId"); AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); - Note revisionNote = notebook.getNoteRevision(noteId, revision, subject); + Note revisionNote = notebook.getNoteByRevision(noteId, revisionId, subject); conn.send(serializeMessage(new Message(OP.NOTE_REVISION) .put("noteId", noteId) - .put("revisionId", revision) + .put("revisionId", revisionId) .put("data", revisionNote))); } @@ -1264,7 +1309,7 @@ public ParagraphListenerImpl(NotebookServer notebookServer, Note note) { @Override public void onProgressUpdate(Job job, int progress) { notebookServer.broadcast( - note.id(), + note.getId(), new Message(OP.PROGRESS).put("id", job.getId()).put("progress", job.progress())); } @@ -1339,15 +1384,15 @@ private void sendAllAngularObjects(Note note, NotebookSocket conn) throws IOExce } for (InterpreterSetting intpSetting : settings) { - AngularObjectRegistry registry = intpSetting.getInterpreterGroup(note.id()) + AngularObjectRegistry registry = intpSetting.getInterpreterGroup(note.getId()) .getAngularObjectRegistry(); - List objects = registry.getAllWithGlobal(note.id()); + List objects = registry.getAllWithGlobal(note.getId()); for (AngularObject object : objects) { conn.send(serializeMessage(new Message(OP.ANGULAR_OBJECT_UPDATE) .put("angularObject", object) .put("interpreterGroupId", - intpSetting.getInterpreterGroup(note.id()).getId()) - .put("noteId", note.id()) + intpSetting.getInterpreterGroup(note.getId()).getId()) + .put("noteId", note.getId()) .put("paragraphId", object.getParagraphId()) )); } @@ -1368,7 +1413,7 @@ public void onUpdate(String interpreterGroupId, AngularObject object) { List notes = notebook.getAllNotes(); for (Note note : notes) { - if (object.getNoteId() != null && !note.id().equals(object.getNoteId())) { + if (object.getNoteId() != null && !note.getId().equals(object.getNoteId())) { continue; } @@ -1379,11 +1424,11 @@ public void onUpdate(String interpreterGroupId, AngularObject object) { } broadcast( - note.id(), + note.getId(), new Message(OP.ANGULAR_OBJECT_UPDATE) .put("angularObject", object) .put("interpreterGroupId", interpreterGroupId) - .put("noteId", note.id()) + .put("noteId", note.getId()) .put("paragraphId", object.getParagraphId())); } } @@ -1393,7 +1438,7 @@ public void onRemove(String interpreterGroupId, String name, String noteId, Stri Notebook notebook = notebook(); List notes = notebook.getAllNotes(); for (Note note : notes) { - if (noteId != null && !note.id().equals(noteId)) { + if (noteId != null && !note.getId().equals(noteId)) { continue; } @@ -1401,7 +1446,7 @@ public void onRemove(String interpreterGroupId, String name, String noteId, Stri for (String id : ids) { if (id.equals(interpreterGroupId)) { broadcast( - note.id(), + note.getId(), new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put( "noteId", noteId).put("paragraphId", paragraphId)); } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/InterpreterSettingListForNoteBind.java b/zeppelin-server/src/main/java/org/apache/zeppelin/types/InterpreterSettingsList.java similarity index 88% rename from zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/InterpreterSettingListForNoteBind.java rename to zeppelin-server/src/main/java/org/apache/zeppelin/types/InterpreterSettingsList.java index 6ec5a54a3ac..e0169167619 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/InterpreterSettingListForNoteBind.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/types/InterpreterSettingsList.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.zeppelin.rest.message; +package org.apache.zeppelin.types; import java.util.List; @@ -24,13 +24,13 @@ /** * InterpreterSetting information for binding */ -public class InterpreterSettingListForNoteBind { +public class InterpreterSettingsList { private String id; private String name; private boolean selected; private List interpreters; - public InterpreterSettingListForNoteBind(String id, String name, + public InterpreterSettingsList(String id, String name, List interpreters, boolean selected) { this.id = id; this.name = name; diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java new file mode 100644 index 00000000000..9333afd9ef8 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/InterpreterBindingUtils.java @@ -0,0 +1,58 @@ +/* + * 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.utils; + +import org.apache.zeppelin.interpreter.InterpreterSetting; +import org.apache.zeppelin.notebook.Notebook; +import org.apache.zeppelin.types.InterpreterSettingsList; + +import java.util.LinkedList; +import java.util.List; + +/** + * Utils for interpreter bindings + */ +public class InterpreterBindingUtils { + public static List getInterpreterBindings(Notebook notebook, + String noteId) { + List settingList = new LinkedList<>(); + List selectedSettings = + notebook.getBindedInterpreterSettings(noteId); + for (InterpreterSetting setting : selectedSettings) { + settingList.add(new InterpreterSettingsList(setting.getId(), setting.getName(), + setting.getInterpreterInfos(), true)); + } + + List availableSettings = notebook.getInterpreterFactory().get(); + for (InterpreterSetting setting : availableSettings) { + boolean selected = false; + for (InterpreterSetting selectedSetting : selectedSettings) { + if (selectedSetting.getId().equals(setting.getId())) { + selected = true; + break; + } + } + + if (!selected) { + settingList.add(new InterpreterSettingsList(setting.getId(), setting.getName(), + setting.getInterpreterInfos(), false)); + } + } + + return settingList; + } +} diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java index 671b213b471..ea3f3637537 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java @@ -124,7 +124,7 @@ private void logoutUser(String userName) { userName + "')]")).click(); ZeppelinITUtils.sleep(500, false); driver.findElement(By.xpath("//div[contains(@class, 'navbar-collapse')]//li[contains(.,'" + - userName + "')]//a[@ng-click='logout()']")).click(); + userName + "')]//a[@ng-click='navbar.logout()']")).click(); ZeppelinITUtils.sleep(5000, false); } @@ -161,12 +161,12 @@ public void testGroupPermission() throws Exception { pollingWait(By.xpath("//span[@tooltip='Note permissions']"), MAX_BROWSER_TIMEOUT_SEC).click(); - pollingWait(By.xpath("//input[@ng-model='permissions.owners']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); - pollingWait(By.xpath("//input[@ng-model='permissions.readers']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); - pollingWait(By.xpath("//input[@ng-model='permissions.writers']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); + pollingWait(By.xpath(".//*[@id='selectOwners']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); + pollingWait(By.xpath(".//*[@id='selectReaders']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); + pollingWait(By.xpath(".//*[@id='selectWriters']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); pollingWait(By.xpath("//button[@ng-click='savePermissions()']"), MAX_BROWSER_TIMEOUT_SEC) .sendKeys(Keys.ENTER); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java index 0ff01353501..500907630de 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java @@ -140,6 +140,25 @@ public void testPySpark() throws Exception { paragraph1Result.getText().toString(), CoreMatchers.equalTo("test loop 0\ntest loop 1\ntest loop 2") ); + // the last statement's evaluation result is printed + setTextOfParagraph(2, "%pyspark\\n" + + "sc.version\\n" + + "1+1"); + runParagraph(2); + try { + waitForParagraph(2, "FINISHED"); + } catch (TimeoutException e) { + waitForParagraph(2, "ERROR"); + collector.checkThat("Paragraph from SparkParagraphIT of testPySpark status: ", + "ERROR", CoreMatchers.equalTo("FINISHED") + ); + } + WebElement paragraph2Result = driver.findElement(By.xpath( + getParagraphXPath(2) + "//div[@class=\"tableDisplay\"]")); + collector.checkThat("Paragraph from SparkParagraphIT of testPySpark result: ", + paragraph2Result.getText().toString(), CoreMatchers.equalTo("2") + ); + } catch (Exception e) { handleException("Exception in SparkParagraphIT while testPySpark", e); } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java index e92432fefaa..c767eb05786 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java @@ -119,13 +119,22 @@ public void testSettingsCRUD() throws IOException { delete.releaseConnection(); } + @Test + public void testSettingsCreateWithEmptyJson() throws IOException { + // Call Create Setting REST API + PostMethod post = httpPost("/interpreter/setting/", ""); + LOG.info("testSettingCRUD create response\n" + post.getResponseBodyAsString()); + assertThat("test create method:", post, isBadRequest()); + post.releaseConnection(); + } + @Test public void testInterpreterAutoBinding() throws IOException { // create note Note note = ZeppelinServer.notebook.createNote(null); // check interpreter is binded - GetMethod get = httpGet("/notebook/interpreter/bind/" + note.id()); + GetMethod get = httpGet("/notebook/interpreter/bind/" + note.getId()); assertThat(get, isAllowed()); get.addRequestHeader("Origin", "http://localhost"); Map resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() { diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java index 94e7fa8b071..d7f55f54744 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java @@ -22,6 +22,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NotebookAuthorization; @@ -122,6 +123,54 @@ public void testPermissions() throws IOException { ZeppelinServer.notebook.removeNote(note2.getId(), null); } + + @Test + public void testGetNoteParagraphJobStatus() throws IOException { + Note note1 = ZeppelinServer.notebook.createNote(null); + note1.addParagraph(); + + String paragraphId = note1.getLastParagraph().getId(); + + GetMethod get = httpGet("/notebook/job/" + note1.getId() + "/" + paragraphId); + assertThat(get, isAllowed()); + Map resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() { + }.getType()); + Map> paragraphStatus = (Map>) resp.get("body"); + + // Check id and status have proper value + assertEquals(paragraphStatus.get("id"), paragraphId); + assertEquals(paragraphStatus.get("status"), "READY"); + + //cleanup + ZeppelinServer.notebook.removeNote(note1.getId(), null); + + } + + @Test + public void testCloneNotebook() throws IOException { + Note note1 = ZeppelinServer.notebook.createNote(null); + PostMethod post = httpPost("/notebook/" + note1.getId(), ""); + LOG.info("testCloneNotebook response\n" + post.getResponseBodyAsString()); + assertThat(post, isCreated()); + Map resp = gson.fromJson(post.getResponseBodyAsString(), new TypeToken>() { + }.getType()); + String clonedNotebookId = (String) resp.get("body"); + post.releaseConnection(); + + GetMethod get = httpGet("/notebook/" + clonedNotebookId); + assertThat(get, isAllowed()); + Map resp2 = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() { + }.getType()); + Map resp2Body = (Map) resp2.get("body"); + + assertEquals((String)resp2Body.get("name"), "Note " + clonedNotebookId); + get.releaseConnection(); + + //cleanup + ZeppelinServer.notebook.removeNote(note1.getId(), null); + ZeppelinServer.notebook.removeNote(clonedNotebookId, null); + + } } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java index 54c31c1fd6a..b4ecd97e375 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java @@ -69,7 +69,7 @@ public void testGetUserList() throws IOException { get.addRequestHeader("Origin", "http://localhost"); Map resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>(){}.getType()); - List userList = (List) resp.get("body"); + List userList = (List) ((Map) resp.get("body")).get("users"); collector.checkThat("Search result size", userList.size(), CoreMatchers.equalTo(1)); collector.checkThat("Search result contains admin", userList.contains("admin"), @@ -80,7 +80,7 @@ public void testGetUserList() throws IOException { notUser.addRequestHeader("Origin", "http://localhost"); Map notUserResp = gson.fromJson(notUser.getResponseBodyAsString(), new TypeToken>(){}.getType()); - List emptyUserList = (List) notUserResp.get("body"); + List emptyUserList = (List) ((Map) notUserResp.get("body")).get("users"); collector.checkThat("Search result size", emptyUserList.size(), CoreMatchers.equalTo(0)); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java index a65ccbcd762..1250f9ce58b 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java @@ -79,7 +79,7 @@ public void basicRDDTransformationAndActionTest() throws IOException { waitForFinish(p); assertEquals(Status.FINISHED, p.getStatus()); assertEquals("55", p.getResult().message()); - ZeppelinServer.notebook.removeNote(note.id(), null); + ZeppelinServer.notebook.removeNote(note.getId(), null); } @Test @@ -91,7 +91,7 @@ public void sparkRTest() throws IOException { if (isSparkR() && sparkVersion >= 14) { // sparkr supported from 1.4.0 // restart spark interpreter List settings = - ZeppelinServer.notebook.getBindedInterpreterSettings(note.id()); + ZeppelinServer.notebook.getBindedInterpreterSettings(note.getId()); for (InterpreterSetting setting : settings) { if (setting.getName().equals("spark")) { @@ -100,13 +100,16 @@ public void sparkRTest() throws IOException { } } - // run markdown paragraph, again + String sqlContextName = "sqlContext"; + if (sparkVersion >= 20) { + sqlContextName = "spark"; + } Paragraph p = note.addParagraph(); Map config = p.getConfig(); config.put("enabled", true); p.setConfig(config); p.setText("%r localDF <- data.frame(name=c(\"a\", \"b\", \"c\"), age=c(19, 23, 18))\n" + - "df <- createDataFrame(sqlContext, localDF)\n" + + "df <- createDataFrame(" + sqlContextName + ", localDF)\n" + "count(df)" ); note.run(p.getId()); @@ -115,7 +118,7 @@ public void sparkRTest() throws IOException { assertEquals(Status.FINISHED, p.getStatus()); assertEquals("[1] 3", p.getResult().message()); } - ZeppelinServer.notebook.removeNote(note.id(), null); + ZeppelinServer.notebook.removeNote(note.getId(), null); } @Test @@ -138,7 +141,7 @@ public void pySparkTest() throws IOException { assertEquals(Status.FINISHED, p.getStatus()); assertEquals("55\n", p.getResult().message()); } - ZeppelinServer.notebook.removeNote(note.id(), null); + ZeppelinServer.notebook.removeNote(note.getId(), null); } @Test @@ -169,7 +172,7 @@ public void pySparkAutoConvertOptionTest() throws IOException { assertEquals(Status.FINISHED, p.getStatus()); assertEquals("10\n", p.getResult().message()); } - ZeppelinServer.notebook.removeNote(note.id(), null); + ZeppelinServer.notebook.removeNote(note.getId(), null); } @Test @@ -201,7 +204,7 @@ public void zRunTest() throws IOException { assertEquals(Status.FINISHED, p2.getStatus()); assertEquals("10", p2.getResult().message()); - ZeppelinServer.notebook.removeNote(note.id(), null); + ZeppelinServer.notebook.removeNote(note.getId(), null); } @Test @@ -213,7 +216,7 @@ public void pySparkDepLoaderTest() throws IOException { if (isPyspark() && sparkVersionNumber >= 14) { // restart spark interpreter List settings = - ZeppelinServer.notebook.getBindedInterpreterSettings(note.id()); + ZeppelinServer.notebook.getBindedInterpreterSettings(note.getId()); for (InterpreterSetting setting : settings) { if (setting.getName().equals("spark")) { diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/server/CorsFilterTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/server/CorsFilterTest.java index a76d4c86a6d..df2a6e92e0d 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/server/CorsFilterTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/server/CorsFilterTest.java @@ -1,7 +1,4 @@ -/** - * Created by joelz on 8/6/15. - * - * +/* * 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. @@ -19,7 +16,6 @@ */ package org.apache.zeppelin.server; -import org.apache.zeppelin.socket.TestHttpServletRequest; import org.junit.Assert; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -27,14 +23,16 @@ import javax.servlet.FilterChain; import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.IOException; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; /** - * BASIC Cors rest api tests + * Basic CORS REST API tests */ public class CorsFilterTest { @@ -42,11 +40,12 @@ public class CorsFilterTest { public static Integer count = 0; @Test + @SuppressWarnings("rawtypes") public void ValidCorsFilterTest() throws IOException, ServletException { CorsFilter filter = new CorsFilter(); HttpServletResponse mockResponse = mock(HttpServletResponse.class); FilterChain mockedFilterChain = mock(FilterChain.class); - TestHttpServletRequest mockRequest = mock(TestHttpServletRequest.class); + HttpServletRequest mockRequest = mock(HttpServletRequest.class); when(mockRequest.getHeader("Origin")).thenReturn("http://localhost:8080"); when(mockRequest.getMethod()).thenReturn("Empty"); when(mockRequest.getServerName()).thenReturn("localhost"); @@ -66,11 +65,12 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { } @Test + @SuppressWarnings("rawtypes") public void InvalidCorsFilterTest() throws IOException, ServletException { CorsFilter filter = new CorsFilter(); HttpServletResponse mockResponse = mock(HttpServletResponse.class); FilterChain mockedFilterChain = mock(FilterChain.class); - TestHttpServletRequest mockRequest = mock(TestHttpServletRequest.class); + HttpServletRequest mockRequest = mock(HttpServletRequest.class); when(mockRequest.getHeader("Origin")).thenReturn("http://evillocalhost:8080"); when(mockRequest.getMethod()).thenReturn("Empty"); when(mockRequest.getServerName()).thenReturn("evillocalhost"); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java index bc131131ea9..01a24e2e1de 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java @@ -1,7 +1,4 @@ -/** - * Created by joelz on 8/6/15. - * - * +/* * 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. @@ -35,10 +32,12 @@ import org.apache.zeppelin.rest.AbstractTestRestApi; import org.apache.zeppelin.server.ZeppelinServer; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import javax.servlet.http.HttpServletRequest; + import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -51,12 +50,13 @@ /** - * BASIC Zeppelin rest api tests + * Basic REST API tests for notebookServer */ public class NotebookServerTest extends AbstractTestRestApi { private static Notebook notebook; private static NotebookServer notebookServer; private static Gson gson; + private HttpServletRequest mockRequest; @BeforeClass public static void init() throws Exception { @@ -71,19 +71,24 @@ public static void destroy() throws Exception { AbstractTestRestApi.shutDown(); } + @Before + public void setUp() { + mockRequest = mock(HttpServletRequest.class); + } + @Test public void checkOrigin() throws UnknownHostException { NotebookServer server = new NotebookServer(); String origin = "http://" + InetAddress.getLocalHost().getHostName() + ":8080"; assertTrue("Origin " + origin + " is not allowed. Please check your hostname.", - server.checkOrigin(new TestHttpServletRequest(), origin)); + server.checkOrigin(mockRequest, origin)); } @Test public void checkInvalidOrigin(){ NotebookServer server = new NotebookServer(); - assertFalse(server.checkOrigin(new TestHttpServletRequest(), "http://evillocalhost:8080")); + assertFalse(server.checkOrigin(mockRequest, "http://evillocalhost:8080")); } @Test @@ -166,7 +171,7 @@ public void testImportNotebook() throws IOException { } @Test - public void should_bind_angular_object_to_remote_for_paragraphs() throws Exception { + public void bindAngularObjectToRemoteForParagraphs() throws Exception { //Given final String varName = "name"; final String value = "DuyHai DOAN"; @@ -191,8 +196,7 @@ public void should_bind_angular_object_to_remote_for_paragraphs() throws Excepti when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup); - - final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); + final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); when(mdRegistry.addAndNotifyRemoteProcess(varName, value, "noteId", "paragraphId")).thenReturn(ao1); @@ -217,7 +221,7 @@ public void should_bind_angular_object_to_remote_for_paragraphs() throws Excepti } @Test - public void should_bind_angular_object_to_local_for_paragraphs() throws Exception { + public void bindAngularObjectToLocalForParagraphs() throws Exception { //Given final String varName = "name"; final String value = "DuyHai DOAN"; @@ -241,7 +245,7 @@ public void should_bind_angular_object_to_local_for_paragraphs() throws Exceptio when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup); - final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); + final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); when(mdRegistry.add(varName, value, "noteId", "paragraphId")).thenReturn(ao1); @@ -264,7 +268,7 @@ public void should_bind_angular_object_to_local_for_paragraphs() throws Exceptio } @Test - public void should_unbind_angular_object_from_remote_for_paragraphs() throws Exception { + public void unbindAngularObjectFromRemoteForParagraphs() throws Exception { //Given final String varName = "name"; final String value = "val"; @@ -286,7 +290,7 @@ public void should_unbind_angular_object_from_remote_for_paragraphs() throws Exc when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup); - final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); + final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); when(mdRegistry.removeAndNotifyRemoteProcess(varName, "noteId", "paragraphId")).thenReturn(ao1); NotebookSocket conn = mock(NotebookSocket.class); NotebookSocket otherConn = mock(NotebookSocket.class); @@ -309,7 +313,7 @@ public void should_unbind_angular_object_from_remote_for_paragraphs() throws Exc } @Test - public void should_unbind_angular_object_from_local_for_paragraphs() throws Exception { + public void unbindAngularObjectFromLocalForParagraphs() throws Exception { //Given final String varName = "name"; final String value = "val"; @@ -331,8 +335,7 @@ public void should_unbind_angular_object_from_local_for_paragraphs() throws Exce when(paragraph.getCurrentRepl().getInterpreterGroup()).thenReturn(mdGroup); - final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); - + final AngularObject ao1 = AngularObjectBuilder.build(varName, value, "noteId", "paragraphId"); when(mdRegistry.remove(varName, "noteId", "paragraphId")).thenReturn(ao1); @@ -355,12 +358,9 @@ public void should_unbind_angular_object_from_local_for_paragraphs() throws Exce private NotebookSocket createWebSocket() { NotebookSocket sock = mock(NotebookSocket.class); - when(sock.getRequest()).thenReturn(createHttpServletRequest()); + when(sock.getRequest()).thenReturn(mockRequest); return sock; } - private HttpServletRequest createHttpServletRequest() { - return mock(HttpServletRequest.class); - } } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/TestHttpServletRequest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/TestHttpServletRequest.java deleted file mode 100644 index 0a176baa4e3..00000000000 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/TestHttpServletRequest.java +++ /dev/null @@ -1,372 +0,0 @@ -/** - * Created by joelz on 8/6/15. - * - * - * 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.socket; - -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.Principal; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Map; - -/** - * Created by joelz on 8/6/15. - * Helps mocking a http servlet request - */ -public class TestHttpServletRequest implements HttpServletRequest { - @Override - public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { - return false; - } - - @Override - public String getAuthType() { - return null; - } - - @Override - public String getContextPath() { - return null; - } - - @Override - public Cookie[] getCookies() { - return new Cookie[0]; - } - - @Override - public long getDateHeader(String s) { - return 0; - } - - @Override - public String getHeader(String s) { - switch (s) { - case "Origin": - return "http://localhost:8080"; - } - - return null; - } - - @Override - public Enumeration getHeaderNames() { - return null; - } - - @Override - public Enumeration getHeaders(String s) { - return null; - } - - @Override - public int getIntHeader(String s) { - return 0; - } - - @Override - public String getMethod() { - return null; - } - - @Override - public Part getPart(String s) throws IOException, ServletException { - return null; - } - - @Override - public Collection getParts() throws IOException, ServletException { - return null; - } - - @Override - public String getPathInfo() { - return null; - } - - @Override - public String getPathTranslated() { - return null; - } - - @Override - public String getQueryString() { - return null; - } - - @Override - public String getRemoteUser() { - return null; - } - - @Override - public String getRequestedSessionId() { - return null; - } - - @Override - public String getRequestURI() { - return null; - } - - @Override - public StringBuffer getRequestURL() { - return null; - } - - @Override - public String getServletPath() { - return null; - } - - @Override - public HttpSession getSession() { - return null; - } - - @Override - public HttpSession getSession(boolean b) { - return null; - } - - @Override - public Principal getUserPrincipal() { - return null; - } - - @Override - public boolean isRequestedSessionIdFromCookie() { - return false; - } - - @Override - public boolean isRequestedSessionIdFromUrl() { - return false; - } - - @Override - public boolean isRequestedSessionIdFromURL() { - return false; - } - - @Override - public boolean isRequestedSessionIdValid() { - return false; - } - - @Override - public boolean isUserInRole(String s) { - return false; - } - - @Override - public void login(String s, String s1) throws ServletException { - - } - - @Override - public void logout() throws ServletException { - - } - - @Override - public AsyncContext getAsyncContext() { - return null; - } - - @Override - public Object getAttribute(String s) { - return null; - } - - @Override - public Enumeration getAttributeNames() { - return null; - } - - @Override - public String getCharacterEncoding() { - return null; - } - - @Override - public int getContentLength() { - return 0; - } - - @Override - public String getContentType() { - return null; - } - - @Override - public DispatcherType getDispatcherType() { - return null; - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return null; - } - - @Override - public String getLocalAddr() { - return null; - } - - @Override - public Locale getLocale() { - return null; - } - - @Override - public Enumeration getLocales() { - return null; - } - - @Override - public String getLocalName() { - return null; - } - - @Override - public int getLocalPort() { - return 0; - } - - @Override - public String getParameter(String s) { - return null; - } - - @Override - public Map getParameterMap() { - return null; - } - - @Override - public Enumeration getParameterNames() { - return null; - } - - @Override - public String[] getParameterValues(String s) { - return new String[0]; - } - - @Override - public String getProtocol() { - return null; - } - - @Override - public BufferedReader getReader() throws IOException { - return null; - } - - @Override - public String getRealPath(String s) { - return null; - } - - @Override - public String getRemoteAddr() { - return null; - } - - @Override - public String getRemoteHost() { - return null; - } - - @Override - public int getRemotePort() { - return 0; - } - - @Override - public RequestDispatcher getRequestDispatcher(String s) { - return null; - } - - @Override - public String getScheme() { - return null; - } - - @Override - public String getServerName() { - return "localhost"; - } - - @Override - public int getServerPort() { - return 0; - } - - @Override - public ServletContext getServletContext() { - return null; - } - - @Override - public boolean isAsyncStarted() { - return false; - } - - @Override - public boolean isAsyncSupported() { - return false; - } - - @Override - public boolean isSecure() { - return false; - } - - @Override - public void removeAttribute(String s) { - - } - - @Override - public void setAttribute(String s, Object o) { - - } - - @Override - public void setCharacterEncoding(String s) throws UnsupportedEncodingException { - - } - - @Override - public AsyncContext startAsync() { - return null; - } - - @Override - public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { - return null; - } -} diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 5d849b3490d..e20493fa7fc 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -31,7 +31,9 @@ "ng-focus-if": "~1.0.2", "bootstrap3-dialog": "bootstrap-dialog#~1.34.7", "handsontable": "~0.24.2", - "moment-duration-format": "^1.3.0" + "moment-duration-format": "^1.3.0", + "select2": "^4.0.3", + "angular-esri-map": "~2.0.0" }, "devDependencies": { "angular-mocks": "1.5.0" diff --git a/zeppelin-web/src/app/app.controller.js b/zeppelin-web/src/app/app.controller.js index 8a0466b4cb0..ff30b829c35 100644 --- a/zeppelin-web/src/app/app.controller.js +++ b/zeppelin-web/src/app/app.controller.js @@ -13,7 +13,7 @@ */ 'use strict'; -angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window) { +angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window, arrayOrderingSrv) { $scope.looknfeel = 'default'; var init = function() { @@ -41,6 +41,12 @@ angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootSc $rootScope.$broadcast('setLookAndFeel', 'default'); }); + $rootScope.noteName = function(note) { + if (!_.isEmpty(note)) { + return arrayOrderingSrv.getNoteName(note); + } + }; + BootstrapDialog.defaultOptions.onshown = function() { angular.element('#' + this.id).find('.btn:last').focus(); }; diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 98d6b879f09..f0f1d89cee5 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -33,7 +33,8 @@ 'xeditable', 'ngToast', 'focus-if', - 'ngResource' + 'ngResource', + 'esri.map' ]) .filter('breakFilter', function() { return function(text) { @@ -98,6 +99,13 @@ var baseUrlSrv = angular.injector(['zeppelinWebApp']).get('baseUrlSrv'); // withCredentials when running locally via grunt $http.defaults.withCredentials = true; + jQuery.ajaxSetup({ + dataType: 'json', + xhrFields: { + withCredentials: true + }, + crossDomain: true + }); return $http.get(baseUrlSrv.getRestApiBase() + '/security/ticket').then(function(response) { zeppelinWebApp.run(function($rootScope) { $rootScope.ticket = angular.fromJson(response.data).body; diff --git a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html index 599248b2e56..1550126b9b7 100644 --- a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html +++ b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html @@ -72,8 +72,8 @@

    Create new interpreter

    - - + +
    @@ -88,6 +88,35 @@

    Create new interpreter

    pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port" />
    +
    +
    + + + +
    +
    + +
    + +
    +
    +

    + Enter comma separated users in the fields.
    + Empty field (*) implies anyone can run this interpreter. +

    +
    + + Owners + +
    +
    +
    +
    + + Properties diff --git a/zeppelin-web/src/app/interpreter/interpreter.controller.js b/zeppelin-web/src/app/interpreter/interpreter.controller.js index d1e8889a25b..378dce4b30b 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.controller.js +++ b/zeppelin-web/src/app/interpreter/interpreter.controller.js @@ -22,8 +22,69 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', $scope.showRepositoryInfo = false; $scope._ = _; + $scope.openPermissions = function() { + $scope.showInterpreterAuth = true; + }; + + $scope.closePermissions = function() { + $scope.showInterpreterAuth = false; + }; + + var getSelectJson = function() { + var selectJson = { + tags: false, + multiple: true, + tokenSeparators: [',', ' '], + minimumInputLength: 2, + ajax: { + url: function(params) { + if (!params.term) { + return false; + } + return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term; + }, + delay: 250, + processResults: function(data, params) { + var users = []; + if (data.body.users.length !== 0) { + for (var i = 0; i < data.body.users.length; i++) { + users.push({ + 'id': data.body.users[i], + 'text': data.body.users[i] + }); + } + } + return { + results: users, + pagination: { + more: false + } + }; + }, + cache: false + } + }; + return selectJson; + }; + + $scope.togglePermissions = function(intpName) { + angular.element('#' + intpName + 'Users').select2(getSelectJson()); + if ($scope.showInterpreterAuth) { + $scope.closePermissions(); + } else { + $scope.openPermissions(); + } + }; + + $scope.$on('ngRenderFinished', function(event, data) { + for (var setting = 0; setting < $scope.interpreterSettings.length; setting++) { + angular.element('#' + $scope.interpreterSettings[setting].name + 'Users').select2(getSelectJson()); + } + }); + var getInterpreterSettings = function() { - $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting').success(function(data, status, headers, config) { + $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting') + .success(function(data, status, headers, config) { $scope.interpreterSettings = data.body; checkDownloadingDependencies(); }).error(function(data, status, headers, config) { @@ -114,7 +175,6 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', var setting = $scope.interpreterSettings[index]; option = setting.option; } - if (option.perNoteSession) { return 'scoped'; } else if (option.perNoteProcess) { @@ -148,10 +208,15 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', if (setting.option.isExistingProcess === undefined) { setting.option.isExistingProcess = false; } + if (setting.option.setPermission === undefined) { + setting.option.setPermission = false; + } if (setting.option.remote === undefined) { // remote always true for now setting.option.remote = true; } + setting.option.users = angular.element('#' + setting.name + 'Users').val(); + var request = { option: angular.copy(setting.option), properties: angular.copy(setting.properties), @@ -168,6 +233,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', removeTMPSettings(index); thisConfirm.close(); checkDownloadingDependencies(); + $route.reload(); }) .error(function(data, status, headers, config) { console.log('Error %o %o', status, data.message); @@ -249,8 +315,8 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', $scope.addNewInterpreterSetting = function() { //user input validation on interpreter creation - if ($scope.newInterpreterSetting.name && - !$scope.newInterpreterSetting.name.trim() || !$scope.newInterpreterSetting.group) { + if (!$scope.newInterpreterSetting.name || + !$scope.newInterpreterSetting.name.trim() || !$scope.newInterpreterSetting.group) { BootstrapDialog.alert({ closable: true, title: 'Add interpreter', @@ -259,6 +325,15 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', return; } + if ($scope.newInterpreterSetting.name.indexOf('.') >= 0) { + BootstrapDialog.alert({ + closable: true, + title: 'Add interpreter', + message: '\'.\' is invalid for interpreter name' + }); + return; + } + if (_.findIndex($scope.interpreterSettings, {'name': $scope.newInterpreterSetting.name}) >= 0) { BootstrapDialog.alert({ closable: true, @@ -275,6 +350,10 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', if (newSetting.depArtifact !== '' || newSetting.depArtifact) { $scope.addNewInterpreterDependency(); } + if (newSetting.option.setPermission === undefined) { + newSetting.option.setPermission = false; + } + newSetting.option.users = angular.element('#newInterpreterUsers').val(); var request = angular.copy($scope.newInterpreterSetting); @@ -311,6 +390,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', option: { remote: true, isExistingProcess: false, + setPermission: false, perNoteSession: false, perNoteProcess: false @@ -487,6 +567,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', var init = function() { $scope.resetNewInterpreterSetting(); $scope.resetNewRepositorySetting(); + getInterpreterSettings(); getAvailableInterpreters(); getRepositories(); diff --git a/zeppelin-web/src/app/interpreter/interpreter.css b/zeppelin-web/src/app/interpreter/interpreter.css index 9843636ae76..ee4f81db614 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.css +++ b/zeppelin-web/src/app/interpreter/interpreter.css @@ -74,6 +74,14 @@ overflow-y: auto; } +.permissionsForm { + list-style-type: none; + background: #EFEFEF; + padding: 10px 10px 10px 10px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); + border: 1px solid #E5E5E5; +} + .interpreterSettingAdd { margin : 5px 5px 5px 5px; padding : 10px 10px 10px 10px; diff --git a/zeppelin-web/src/app/interpreter/interpreter.html b/zeppelin-web/src/app/interpreter/interpreter.html index 3c25246067a..7ac18728093 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.html +++ b/zeppelin-web/src/app/interpreter/interpreter.html @@ -84,9 +84,10 @@

    Repositories

    + ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: searchInterpreter" interpreter-directive>
    +

    {{setting.name}} @@ -173,8 +174,8 @@
    Option

    - -
    @@ -191,7 +192,33 @@
    Option
    pu-elastic-input-minwidth="180px" ng-model="setting.option.port" ng-disabled="!valueform.$visible" />
    +
    +
    + + + +
    +
    +
    + +
    +
    +

    + Enter comma separated users in the fields.
    + Empty field (*) implies anyone can run this interpreter. +

    +
    + + Owners + +
    +
    +
    +
    Currently there are no properties and dependencies set for this interpreter diff --git a/zeppelin-web/src/app/notebook/notebook-actionBar.html b/zeppelin-web/src/app/notebook/notebook-actionBar.html index cf13d50aefa..22ae67c64d1 100644 --- a/zeppelin-web/src/app/notebook/notebook-actionBar.html +++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html @@ -13,10 +13,11 @@ -->

    -
    +
    -

    {{noteName(note)}}

    + ng-if="input.showEditor" ng-model="note.name" ng-blur="sendNewName();input.showEditor = false;" ng-enter="sendNewName();input.showEditor = false;" ng-escape="note.name = oldName; input.showEditor = false" focus-if="input.showEditor" /> +

    {{noteName(note)}}

    diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index dc59f50ae71..f2dce0964ee 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -18,7 +18,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro baseUrlSrv, $timeout, saveAsService) { $scope.note = null; $scope.moment = moment; - $scope.showEditor = false; $scope.editorToggled = false; $scope.tableToggled = false; $scope.viewOnly = false; @@ -43,16 +42,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro var connectedOnce = false; // user auto complete related - $scope.suggestions = []; - $scope.selectIndex = -1; - var selectedUser = ''; - var selectedUserIndex = 0; - var previousSelectedList = []; - var previousSelectedListOwners = []; - var previousSelectedListReaders = []; - var previousSelectedListWriters = []; - var searchText = []; - $scope.role = ''; $scope.noteRevisions = []; $scope.$on('setConnectedStatus', function(event, param) { @@ -79,7 +68,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro var initNotebook = function() { websocketMsgSrv.getNotebook($routeParams.noteId); websocketMsgSrv.listRevisionHistory($routeParams.noteId); - var currentRoute = $route.current; if (currentRoute) { setTimeout( @@ -344,6 +332,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } else { $scope.viewOnly = $scope.note.config.looknfeel === 'report' ? true : false; } + $scope.note.paragraphs[0].focus = true; $rootScope.$broadcast('setLookAndFeel', $scope.note.config.looknfeel); }; @@ -449,23 +438,14 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } }; - var getInterpreterBindings = function(callback) { - $http.get(baseUrlSrv.getRestApiBase() + '/notebook/interpreter/bind/' + $scope.note.id). - success(function(data, status, headers, config) { - $scope.interpreterBindings = data.body; - $scope.interpreterBindingsOrig = angular.copy($scope.interpreterBindings); // to check dirty - if (callback) { - callback(); - } - }). - error(function(data, status, headers, config) { - if (status !== 0) { - console.log('Error %o %o', status, data.message); - } - }); + var getInterpreterBindings = function() { + websocketMsgSrv.getInterpreterBindings($scope.note.id); }; - var getInterpreterBindingsCallBack = function() { + $scope.$on('interpreterBindings', function(event, data) { + $scope.interpreterBindings = data.interpreterBindings; + $scope.interpreterBindingsOrig = angular.copy($scope.interpreterBindings); // to check dirty + var selected = false; var key; var setting; @@ -490,7 +470,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } $scope.showSetting = true; } - }; + }); $scope.interpreterSelectionListeners = { accept: function(sourceItemHandleScope, destSortableScope) {return true;}, @@ -530,16 +510,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro selectedSettingIds.push(setting.id); } } - - $http.put(baseUrlSrv.getRestApiBase() + '/notebook/interpreter/bind/' + $scope.note.id, - selectedSettingIds). - success(function(data, status, headers, config) { - console.log('Interpreter binding %o saved', selectedSettingIds); - $scope.showSetting = false; - }). - error(function(data, status, headers, config) { - console.log('Error %o %o', status, data.message); - }); + websocketMsgSrv.saveInterpreterBindings($scope.note.id, selectedSettingIds); + console.log('Interpreter bindings %o saved', selectedSettingIds); + $scope.showSetting = false; }; $scope.toggleSetting = function() { @@ -556,6 +529,63 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro success(function(data, status, headers, config) { $scope.permissions = data.body; $scope.permissionsOrig = angular.copy($scope.permissions); // to check dirty + + var selectJson = { + tokenSeparators: [',', ' '], + ajax: { + url: function(params) { + if (!params.term) { + return false; + } + return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term; + }, + delay: 250, + processResults: function(data, params) { + var results = []; + + if (data.body.users.length !== 0) { + var users = []; + for (var len = 0; len < data.body.users.length; len++) { + users.push({ + 'id': data.body.users[len], + 'text': data.body.users[len] + }); + } + results.push({ + 'text': 'Users :', + 'children': users + }); + } + if (data.body.roles.length !== 0) { + var roles = []; + for (var len = 0; len < data.body.roles.length; len++) { + roles.push({ + 'id': data.body.roles[len], + 'text': data.body.roles[len] + }); + } + results.push({ + 'text': 'Roles :', + 'children': roles + }); + } + return { + results: results, + pagination: { + more: false + } + }; + }, + cache: false + }, + width: ' ', + tags: true, + minimumInputLength: 3 + }; + + angular.element('#selectOwners').select2(selectJson); + angular.element('#selectReaders').select2(selectJson); + angular.element('#selectWriters').select2(selectJson); if (callback) { callback(); } @@ -592,15 +622,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro }; function convertPermissionsToArray() { - if (!angular.isArray($scope.permissions.owners)) { - $scope.permissions.owners = $scope.permissions.owners.split(','); - } - if (!angular.isArray($scope.permissions.readers)) { - $scope.permissions.readers = $scope.permissions.readers.split(','); - } - if (!angular.isArray($scope.permissions.writers)) { - $scope.permissions.writers = $scope.permissions.writers.split(','); - } + $scope.permissions.owners = angular.element('#selectOwners').val(); + $scope.permissions.readers = angular.element('#selectReaders').val(); + $scope.permissions.writers = angular.element('#selectWriters').val(); } $scope.savePermissions = function() { @@ -652,6 +676,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro $scope.togglePermissions = function() { if ($scope.showPermissions) { $scope.closePermissions(); + angular.element('#selectOwners').select2({}); + angular.element('#selectReaders').select2({}); + angular.element('#selectWriters').select2({}); } else { $scope.openPermissions(); $scope.closeSetting(); @@ -674,195 +701,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } }; - function checkPreviousRole(role) { - var i = 0; - if (role !== $scope.role) { - if ($scope.role === 'owners') { - previousSelectedListOwners = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListOwners[i] = previousSelectedList[i]; - } - } - if ($scope.role === 'readers') { - previousSelectedListReaders = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListReaders[i] = previousSelectedList[i]; - } - } - if ($scope.role === 'writers') { - previousSelectedListWriters = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListWriters[i] = previousSelectedList[i]; - } - } - - $scope.role = role; - previousSelectedList = []; - if (role === 'owners') { - for (i = 0; i < previousSelectedListOwners.length; i++) { - previousSelectedList[i] = previousSelectedListOwners[i]; - } - } - if (role === 'readers') { - for (i = 0; i < previousSelectedListReaders.length; i++) { - previousSelectedList[i] = previousSelectedListReaders[i]; - } - } - if (role === 'writers') { - for (i = 0; i < previousSelectedListWriters.length; i++) { - previousSelectedList[i] = previousSelectedListWriters[i]; - } - } - } - } - - function convertToArray(role) { - if (!$scope.permissions) { - return; - } else if (role === 'owners' && typeof $scope.permissions.owners === 'string') { - searchText = $scope.permissions.owners.split(','); - } else if (role === 'readers' && typeof $scope.permissions.readers === 'string') { - searchText = $scope.permissions.readers.split(','); - } else if (role === 'writers' && typeof $scope.permissions.writers === 'string') { - searchText = $scope.permissions.writers.split(','); - } - - for (var i = 0; i < searchText.length; i++) { - searchText[i] = searchText[i].trim(); - } - } - - function convertToString(role) { - if (role === 'owners') { - $scope.permissions.owners = searchText.join(); - } else if (role === 'readers') { - $scope.permissions.readers = searchText.join(); - } else if (role === 'writers') { - $scope.permissions.writers = searchText.join(); - } - } - - function getSuggestions(searchQuery) { - $scope.suggestions = []; - $http.get(baseUrlSrv.getRestApiBase() + '/security/userlist/' + searchQuery).then(function - (response) { - var userlist = angular.fromJson(response.data).body; - for (var k in userlist) { - $scope.suggestions.push(userlist[k]); - } - }); - } - - function updatePreviousList() { - for (var i = 0; i < searchText.length; i++) { - previousSelectedList[i] = searchText[i]; - } - } - - var getChangedIndex = function() { - if (previousSelectedList.length === 0) { - selectedUserIndex = searchText.length - 1; - } else { - for (var i = 0; i < searchText.length; i++) { - if (previousSelectedList[i] !== searchText[i]) { - selectedUserIndex = i; - previousSelectedList = []; - break; - } - } - } - updatePreviousList(); - }; - - $scope.$watch('permissions.owners', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('owners'); - }); - }, 350)); - - $scope.$watch('permissions.readers', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('readers'); - }); - }, 350)); - - $scope.$watch('permissions.writers', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('writers'); - }); - }, 350)); - - // function to find suggestion list on change - $scope.search = function(role) { - angular.element('.userlist').show(); - convertToArray(role); - checkPreviousRole(role); - getChangedIndex(); - $scope.selectIndex = -1; - $scope.suggestions = []; - selectedUser = searchText[selectedUserIndex]; - if (selectedUser !== '') { - getSuggestions(selectedUser); - } else { - $scope.suggestions = []; - } - }; - - var checkIfSelected = function() { - if (($scope.suggestions.length === 0) && - ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length) || - ($scope.suggestions.length !== 0 && ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length)) - ) { - searchText[selectedUserIndex] = selectedUser; - $scope.suggestions = []; - return true; - } else { - return false; - } - }; - - $scope.checkKeyDown = function(event, role) { - if (event.keyCode === 40) { - event.preventDefault(); - if ($scope.selectIndex + 1 !== $scope.suggestions.length) { - $scope.selectIndex++; - } - } else if (event.keyCode === 38) { - event.preventDefault(); - - if ($scope.selectIndex - 1 !== -1) { - $scope.selectIndex--; - - } - } else if (event.keyCode === 13) { - event.preventDefault(); - if (!checkIfSelected()) { - selectedUser = $scope.suggestions[$scope.selectIndex]; - searchText[selectedUserIndex] = $scope.suggestions[$scope.selectIndex]; - updatePreviousList(); - convertToString(role); - $scope.suggestions = []; - } - } - }; - - $scope.checkKeyUp = function(event) { - if (event.keyCode !== 8 || event.keyCode !== 46) { - if (searchText[selectedUserIndex] === '') { - $scope.suggestions = []; - } - } - }; - - $scope.assignValueAndHide = function(index, role) { - searchText[selectedUserIndex] = $scope.suggestions[index]; - updatePreviousList(); - convertToString(role); - $scope.suggestions = []; - }; - angular.element(document).click(function() { - angular.element('.userlist').hide(); angular.element('.ace_autocomplete').hide(); }); @@ -983,7 +822,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } initializeLookAndFeel(); //open interpreter binding setting when there're none selected - getInterpreterBindings(getInterpreterBindingsCallBack); + getInterpreterBindings(); }); $scope.$on('$destroy', function() { diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css index ed45c67bc70..c11544fd0d2 100644 --- a/zeppelin-web/src/app/notebook/notebook.css +++ b/zeppelin-web/src/app/notebook/notebook.css @@ -308,51 +308,7 @@ cursor: default; } -.userlist { - width: 230px; - font-family: Georgia, Times, serif; - font-size: 15px; - position: absolute; - z-index: 9999; -} - -.userlist ul { - list-style: none; -} - -.userlist ul li { - box-shadow: 3px 3px 5px #888888; - display: list-item; - text-decoration: none; - color: #000000; - background-color: #FFFFFF; - line-height: 30px; - border-bottom-style: none; - border-bottom-width: 1px; - border-bottom:1px #CCCCCC solid; - padding-left: 10px; - cursor: pointer; -} - -.userlist ul li:first-child { - border-top-right-radius: 5px; - border-top-left-radius: 5px; -} - -.userlist ul li:last-child { - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; -} - -.userlist ul li strong { - margin-right: 10px; -} - -.userlist li:hover { - background-color: #E0E0E0; -} - -.userlist li:active, -.userlist li.active { - background-color: #428BCA; +.select2-container--default{ + min-width: 150px; + max-width: 50%; } diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html index fd329ac18e8..9ad71662305 100644 --- a/zeppelin-web/src/app/notebook/notebook.html +++ b/zeppelin-web/src/app/notebook/notebook.html @@ -70,57 +70,24 @@

    Note Permissions (Only note owners can change)

    -

    Owners - - Owners can change permissions,read and write the note. +

    Owners + + Owners can change permissions,read and write the note.

    -
    -
      -
    • - {{suggestion}} -
    • -
    -

    Readers - + Readers can only read the note.

    -
    -
      -
    • - {{suggestion}} -
    • -
    -

    Writers - + Writers can read and write the note.

    -
    -
      -
    • - {{suggestion}} -
    • -
    -

    diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html index 0c763189fa5..eab741b26cb 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html @@ -20,32 +20,44 @@ +
    + +
    +
    + + Maps require internet connectivity. + +

    diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-graphOptions.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-graphOptions.html index 496f1b79cd4..b1c58023f18 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph-graphOptions.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-graphOptions.html @@ -28,4 +28,19 @@ show line chart with focus
    +
    + + + + + +
    +

    diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-pivot.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-pivot.html index 66f570b183f..9ddf9820640 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph-pivot.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-pivot.html @@ -32,7 +32,7 @@
    -
    +
    Keys @@ -165,4 +165,52 @@
    + +
    +
    + + Latitude +
      +
    • +
      + {{paragraph.config.graph.map.lat.name}} +
      +
    • +
    +
    +
    +
    + + Longitude +
      +
    • +
      + {{paragraph.config.graph.map.lng.name}} +
      +
    • +
    +
    +
    +
    + + Pin contents +
      +
    • +
      + {{col.name}} +
      +
    • +
    +
    +
    +
    diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 1d672e75cbb..bd3b6b3c945 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -16,7 +16,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window, $routeParams, $location, $timeout, $compile, $http, websocketMsgSrv, baseUrlSrv, ngToast, - saveAsService) { + saveAsService, esriLoader) { var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.parentNote = null; $scope.paragraph = null; @@ -93,13 +93,12 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.parentNote = note; $scope.originalText = angular.copy(newParagraph.text); $scope.chart = {}; + $scope.baseMapOption = ['Streets', 'Satellite', 'Hybrid', 'Topo', 'Gray', 'Oceans', 'Terrain']; $scope.colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - $scope.showTitleEditor = false; $scope.paragraphFocused = false; if (newParagraph.focus) { $scope.paragraphFocused = true; } - if (!$scope.paragraph.config) { $scope.paragraph.config = {}; } @@ -246,6 +245,22 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r config.graph.scatter = {}; } + if (!config.graph.map) { + config.graph.map = {}; + } + + if (!config.graph.map.baseMapType) { + config.graph.map.baseMapType = $scope.baseMapOption[0]; + } + + if (!config.graph.map.isOnline) { + config.graph.map.isOnline = true; + } + + if (!config.graph.map.pinCols) { + config.graph.map.pinCols = []; + } + if (config.enabled === undefined) { config.enabled = true; } @@ -523,10 +538,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.aceChanged = function() { $scope.dirtyText = $scope.editor.getSession().getValue(); $scope.startSaveTimer(); - - $timeout(function() { - $scope.setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); - }); + $scope.setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); }; $scope.aceLoaded = function(_editor) { @@ -535,6 +547,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r _editor.$blockScrolling = Infinity; $scope.editor = _editor; + $scope.editor.on('input', $scope.aceChanged); if (_editor.container.id !== '{{paragraph.id}}_editor') { $scope.editor.renderer.setShowGutter($scope.paragraph.config.lineNumbers); $scope.editor.setShowFoldWidgets(false); @@ -542,9 +555,10 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.editor.setHighlightGutterLine(false); $scope.editor.getSession().setUseWrapMode(true); $scope.editor.setTheme('ace/theme/chrome'); + $scope.editor.setReadOnly($scope.isRunning()); if ($scope.paragraphFocused) { $scope.editor.focus(); - $scope.goToLineEnd(); + $scope.goToEnd(); } autoAdjustEditorHeight(_editor.container.id); @@ -625,13 +639,15 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r enableLiveAutocompletion: false }); - $scope.handleFocus = function(value) { + $scope.handleFocus = function(value, isDigestPass) { $scope.paragraphFocused = value; - // Protect against error in case digest is already running - $timeout(function() { - // Apply changes since they come from 3rd party library - $scope.$digest(); - }); + if (isDigestPass === false || isDigestPass === undefined) { + // Protect against error in case digest is already running + $timeout(function() { + // Apply changes since they come from 3rd party library + $scope.$digest(); + }); + } }; $scope.editor.on('focus', function() { @@ -668,6 +684,19 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.editor.commands.bindKey('ctrl-.', 'startAutocomplete'); $scope.editor.commands.bindKey('ctrl-space', null); + var keyBindingEditorFocusAction = function(scrollValue) { + var numRows = $scope.editor.getSession().getLength(); + var currentRow = $scope.editor.getCursorPosition().row; + if (currentRow === 0 && scrollValue <= 0) { + // move focus to previous paragraph + $scope.$emit('moveFocusToPreviousParagraph', $scope.paragraph.id); + } else if (currentRow === numRows - 1 && scrollValue >= 0) { + $scope.$emit('moveFocusToNextParagraph', $scope.paragraph.id); + } else { + $scope.scrollToCursor($scope.paragraph.id, scrollValue); + } + }; + // handle cursor moves $scope.editor.keyBinding.origOnCommandKey = $scope.editor.keyBinding.onCommandKey; $scope.editor.keyBinding.onCommandKey = function(e, hashId, keyCode) { @@ -681,27 +710,26 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r angular.element('#' + $scope.paragraph.id + '_editor > textarea').css('top', cursorPos.top); } - var numRows; - var currentRow; + var ROW_UP = -1; + var ROW_DOWN = 1; - if (keyCode === 38 || (keyCode === 80 && e.ctrlKey && !e.altKey)) { // UP - numRows = $scope.editor.getSession().getLength(); - currentRow = $scope.editor.getCursorPosition().row; - if (currentRow === 0) { - // move focus to previous paragraph - $scope.$emit('moveFocusToPreviousParagraph', $scope.paragraph.id); - } else { - $scope.scrollToCursor($scope.paragraph.id, -1); - } - } else if (keyCode === 40 || (keyCode === 78 && e.ctrlKey && !e.altKey)) { // DOWN - numRows = $scope.editor.getSession().getLength(); - currentRow = $scope.editor.getCursorPosition().row; - if (currentRow === numRows - 1) { - // move focus to next paragraph - $scope.$emit('moveFocusToNextParagraph', $scope.paragraph.id); - } else { - $scope.scrollToCursor($scope.paragraph.id, 1); - } + switch (keyCode) { + case 38: + keyBindingEditorFocusAction(ROW_UP); + break; + case 80: + if (e.ctrlKey && !e.altKey) { + keyBindingEditorFocusAction(ROW_UP); + } + break; + case 40: + keyBindingEditorFocusAction(ROW_DOWN); + break; + case 78: + if (e.ctrlKey && !e.altKey) { + keyBindingEditorFocusAction(ROW_DOWN); + } + break; } } this.origOnCommandKey(e, hashId, keyCode); @@ -792,7 +820,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } var user = (pdata.user === undefined || pdata.user === null) ? 'anonymous' : pdata.user; var desc = 'Took ' + moment.duration((timeMs / 1000), 'seconds').format('h [hrs] m [min] s [sec]') + - '. Last updated by ' + user + ' at ' + moment(pdata.dateUpdated).format('MMMM DD YYYY, h:mm:ss A') + '.'; + '. Last updated by ' + user + ' at ' + moment(pdata.dateFinished).format('MMMM DD YYYY, h:mm:ss A') + '.'; if ($scope.isResultOutdated()) { desc += ' (outdated)'; } @@ -811,8 +839,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r return false; }; - $scope.goToLineEnd = function() { - $scope.editor.navigateLineEnd(); + $scope.goToEnd = function() { + $scope.editor.navigateFileEnd(); }; $scope.getResultType = function(paragraph) { @@ -837,6 +865,21 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; + $scope.parseTableCell = function(cell) { + if (!isNaN(cell)) { + if (cell.length === 0 || Number(cell) > Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) { + return cell; + } else { + return Number(cell); + } + } + var d = moment(cell); + if (d.isValid()) { + return d; + } + return cell; + }; + $scope.loadTableData = function(result) { if (!result) { return; @@ -870,8 +913,9 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if (i === 0) { columnNames.push({name: col, index: j, aggr: 'sum'}); } else { - cols.push(col); - cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: col}); + var parsedCol = $scope.parseTableCell(col); + cols.push(parsedCol); + cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: parsedCol}); } } if (i !== 0) { @@ -896,6 +940,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if (!type || type === 'table') { setTable($scope.paragraph.result, refresh); + } else if (type === 'map') { + setMap($scope.paragraph.result, refresh); } else { setD3Chart(type, $scope.paragraph.result, refresh); } @@ -948,7 +994,9 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r cells: function(row, col, prop) { var cellProperties = {}; cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) { - if (!isNaN(value)) { + if (value instanceof moment) { + td.innerHTML = value._i; + } else if (!isNaN(value)) { cellProperties.format = '0,0.[00000]'; td.style.textAlign = 'left'; Handsontable.renderers.NumericRenderer.apply(this, arguments); @@ -999,7 +1047,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }; var yAxisTickFormat = function(d) { - if (d >= Math.pow(10,6)) { + if (Math.abs(d) >= Math.pow(10,6)) { return customAbbrevFormatter(d); } return groupedThousandsWith3DigitsFormatter(d); @@ -1023,7 +1071,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r d3g = scatterData.d3g; $scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);}); - $scope.chart[type].yAxis.tickFormat(function(d) {return xAxisTickFormat(d, yLabels);}); + $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, yLabels);}); // configure how the tooltip looks. $scope.chart[type].tooltipContent(function(key, x, y, graph, data) { @@ -1065,7 +1113,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r xLabels = pivotdata.xLabels; d3g = pivotdata.d3g; $scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);}); - $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d);}); + if (type === 'stackedAreaChart') { + $scope.chart[type].yAxisTickFormat(function(d) {return yAxisTickFormat(d);}); + } else { + $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, xLabels);}); + } $scope.chart[type].yAxis.axisLabelDistance(50); if ($scope.chart[type].useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline $scope.chart[type].useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691) @@ -1120,6 +1172,236 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $timeout(retryRenderer); }; + var setMap = function(data, refresh) { + var createPinMapLayer = function(pins, cb) { + esriLoader.require(['esri/layers/FeatureLayer'], function(FeatureLayer) { + var pinLayer = new FeatureLayer({ + id: 'pins', + spatialReference: $scope.map.spatialReference, + geometryType: 'point', + source: pins, + fields: [], + objectIdField: '_ObjectID', + renderer: $scope.map.pinRenderer, + popupTemplate: { + title: '[{_lng}, {_lat}]', + content: [{ + type: 'fields', + fieldInfos: [] + }] + } + }); + + // add user-selected pin info fields to popup + var pinInfoCols = $scope.paragraph.config.graph.map.pinCols; + for (var i = 0; i < pinInfoCols.length; ++i) { + pinLayer.popupTemplate.content[0].fieldInfos.push({ + fieldName: pinInfoCols[i].name, + visible: true + }); + } + cb(pinLayer); + }); + }; + + var getMapPins = function(cb) { + esriLoader.require(['esri/geometry/Point'], function(Point, FeatureLayer) { + var latCol = $scope.paragraph.config.graph.map.lat; + var lngCol = $scope.paragraph.config.graph.map.lng; + var pinInfoCols = $scope.paragraph.config.graph.map.pinCols; + var pins = []; + + // construct objects for pins + if (latCol && lngCol && data.rows) { + for (var i = 0; i < data.rows.length; ++i) { + var row = data.rows[i]; + var lng = row[lngCol.index]; + var lat = row[latCol.index]; + var pin = { + geometry: new Point({ + longitude: lng, + latitude: lat, + spatialReference: $scope.map.spatialReference + }), + attributes: { + _ObjectID: i, + _lng: lng, + _lat: lat + } + }; + + // add pin info from user-selected columns + for (var j = 0; j < pinInfoCols.length; ++j) { + var col = pinInfoCols[j]; + pin.attributes[col.name] = row[col.index]; + } + pins.push(pin); + } + } + cb(pins); + }); + }; + + var updateMapPins = function() { + var pinLayer = $scope.map.map.findLayerById('pins'); + $scope.map.popup.close(); + if (pinLayer) { + $scope.map.map.remove(pinLayer); + } + + // add pins to map as layer + getMapPins(function(pins) { + createPinMapLayer(pins, function(pinLayer) { + $scope.map.map.add(pinLayer); + if (pinLayer.source.length > 0) { + $scope.map.goTo(pinLayer.source); + } + }); + }); + }; + + var createMap = function(mapdiv) { + // prevent zooming with the scroll wheel + var disableZoom = function(e) { + var evt = e || window.event; + evt.cancelBubble = true; + evt.returnValue = false; + if (evt.stopPropagation) { + evt.stopPropagation(); + } + }; + var eName = window.WheelEvent ? 'wheel' : // Modern browsers + window.MouseWheelEvent ? 'mousewheel' : // WebKit and IE + 'DOMMouseScroll'; // Old Firefox + mapdiv.addEventListener(eName, disableZoom, true); + + esriLoader.require(['esri/views/MapView', + 'esri/Map', + 'esri/renderers/SimpleRenderer', + 'esri/symbols/SimpleMarkerSymbol'], + function(MapView, Map, SimpleRenderer, SimpleMarkerSymbol) { + $scope.map = new MapView({ + container: mapdiv, + map: new Map({ + basemap: $scope.paragraph.config.graph.map.baseMapType.toLowerCase() + }), + center: [-106.3468, 56.1304], // Canada (lng, lat) + zoom: 2, + pinRenderer: new SimpleRenderer({ + symbol: new SimpleMarkerSymbol({ + 'color': [255, 0, 0, 0.5], + 'size': 16.5, + 'outline': { + 'color': [0, 0, 0, 1], + 'width': 1.125, + }, + // map pin SVG path + 'path': 'M16,3.5c-4.142,0-7.5,3.358-7.5,7.5c0,4.143,7.5,18.121,7.5,' + + '18.121S23.5,15.143,23.5,11C23.5,6.858,20.143,3.5,16,3.5z ' + + 'M16,14.584c-1.979,0-3.584-1.604-3.584-3.584S14.021,7.416,' + + '16,7.416S19.584,9.021,19.584,11S17.979,14.584,16,14.584z' + }) + }) + }); + + $scope.map.on('click', function() { + // ArcGIS JS API 4.0 does not account for scrolling or position + // changes by default (this is a bug, to be fixed in the upcoming + // version 4.1; see https://geonet.esri.com/thread/177238#comment-609681). + // This results in a misaligned popup. + + // Workaround: manually set popup position to match position of selected pin + if ($scope.map.popup.selectedFeature) { + $scope.map.popup.location = $scope.map.popup.selectedFeature.geometry; + } + }); + $scope.map.then(updateMapPins); + }); + }; + + var checkMapOnline = function(cb) { + // are we able to get a response from the ArcGIS servers? + var callback = function(res) { + var online = (res.status > 0); + $scope.paragraph.config.graph.map.isOnline = online; + cb(online); + }; + $http.head('//services.arcgisonline.com/arcgis/', { + timeout: 5000, + withCredentials: false + }).then(callback, callback); + }; + + var renderMap = function() { + var mapdiv = angular.element('#p' + $scope.paragraph.id + '_map') + .css('height', $scope.paragraph.config.graph.height) + .children('div').get(0); + + // on chart type change, destroy map to force reinitialization. + if ($scope.map && !refresh) { + $scope.map.map.destroy(); + $scope.map.pinRenderer = null; + $scope.map = null; + } + + var requireMapCSS = function() { + var url = '//js.arcgis.com/4.0/esri/css/main.css'; + if (!angular.element('link[href="' + url + '"]').length) { + var link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = url; + angular.element('head').append(link); + } + }; + + var requireMapJS = function(cb) { + if (!esriLoader.isLoaded()) { + esriLoader.bootstrap({ + url: '//js.arcgis.com/4.0' + }).then(cb); + } else { + cb(); + } + }; + + checkMapOnline(function(online) { + // we need an internet connection to use the map + if (online) { + // create map if not exists. + if (!$scope.map) { + requireMapCSS(); + requireMapJS(function() { + createMap(mapdiv); + }); + } else { + updateMapPins(); + } + } + }); + }; + + var retryRenderer = function() { + if (angular.element('#p' + $scope.paragraph.id + '_map div').length) { + try { + renderMap(); + } catch (err) { + console.log('Map drawing error %o', err); + } + } else { + $timeout(retryRenderer,10); + } + }; + $timeout(retryRenderer); + }; + + $scope.setMapBaseMap = function(bm) { + $scope.paragraph.config.graph.map.baseMapType = bm; + if ($scope.map) { + $scope.map.map.basemap = bm.toLowerCase(); + } + }; + $scope.isGraphMode = function(graphName) { var activeAppId = _.get($scope.paragraph.config, 'helium.activeApp'); if ($scope.getResultType() === 'TABLE' && $scope.getGraphMode() === graphName && !activeAppId) { @@ -1182,6 +1464,24 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.setGraphMode($scope.paragraph.config.graph.mode, true, false); }; + $scope.removeMapOptionLat = function(idx) { + $scope.paragraph.config.graph.map.lat = null; + clearUnknownColsFromGraphOption(); + $scope.setGraphMode($scope.paragraph.config.graph.mode, true, false); + }; + + $scope.removeMapOptionLng = function(idx) { + $scope.paragraph.config.graph.map.lng = null; + clearUnknownColsFromGraphOption(); + $scope.setGraphMode($scope.paragraph.config.graph.mode, true, false); + }; + + $scope.removeMapOptionPinInfo = function(idx) { + $scope.paragraph.config.graph.map.pinCols.splice(idx, 1); + clearUnknownColsFromGraphOption(); + $scope.setGraphMode($scope.paragraph.config.graph.mode, true, false); + }; + /* Clear unknown columns from graph option */ var clearUnknownColsFromGraphOption = function() { var unique = function(list) { @@ -1212,7 +1512,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; - var removeUnknownFromScatterSetting = function(fields) { + var removeUnknownFromFields = function(fields) { for (var f in fields) { if (fields[f]) { var found = false; @@ -1224,7 +1524,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r break; } } - if (!found) { + if (!found && (fields[f] instanceof Object) && !(fields[f] instanceof Array)) { fields[f] = null; } } @@ -1239,7 +1539,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r unique($scope.paragraph.config.graph.groups); removeUnknown($scope.paragraph.config.graph.groups); - removeUnknownFromScatterSetting($scope.paragraph.config.graph.scatter); + removeUnknownFromFields($scope.paragraph.config.graph.scatter); + + unique($scope.paragraph.config.graph.map.pinCols); + removeUnknown($scope.paragraph.config.graph.map.pinCols); + removeUnknownFromFields($scope.paragraph.config.graph.map); }; /* select default key and value if there're none selected */ @@ -1260,6 +1564,23 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.paragraph.config.graph.scatter.xAxis = $scope.paragraph.result.columnNames[0]; } } + + /* try to find columns for the map logitude and latitude */ + var findDefaultMapCol = function(settingName, keyword) { + var col; + if (!$scope.paragraph.config.graph.map[settingName]) { + for (var i = 0; i < $scope.paragraph.result.columnNames.length; ++i) { + col = $scope.paragraph.result.columnNames[i]; + if (col.name.toUpperCase().indexOf(keyword) !== -1) { + $scope.paragraph.config.graph.map[settingName] = col; + break; + } + } + } + }; + + findDefaultMapCol('lat', 'LAT'); + findDefaultMapCol('lng', 'LONG'); }; var pivot = function(data) { @@ -1533,7 +1854,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r if (groups.length === 1 && values.length === 1) { for (d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) { colName = d3g[d3gIndex].key; - colName = colName.split('.')[0]; + colName = colName.split('.').slice(0, -1).join('.'); d3g[d3gIndex].key = colName; } } @@ -2217,6 +2538,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.paragraph.status = data.paragraph.status; $scope.paragraph.result = data.paragraph.result; $scope.paragraph.settings = data.paragraph.settings; + $scope.editor.setReadOnly($scope.isRunning()); if (!$scope.asIframe) { $scope.paragraph.config = data.paragraph.config; @@ -2271,7 +2593,16 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r }); $scope.$on('appendParagraphOutput', function(event, data) { - if ($scope.paragraph.id === data.paragraphId) { + /* It has been observed that append events + * can be errorneously called even if paragraph + * execution has ended, and in that case, no append + * should be made. Also, it was observed that between PENDING + * and RUNNING states, append-events can be called and we can't + * miss those, else during the length of paragraph run, few + * initial output line/s will be missing. + */ + if ($scope.paragraph.id === data.paragraphId && + ($scope.paragraph.status === 'RUNNING' || $scope.paragraph.status === 'PENDING')) { if ($scope.flushStreamingOutput) { $scope.clearTextOutput(); $scope.flushStreamingOutput = false; @@ -2374,7 +2705,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.handleFocus(true); } else { $scope.editor.blur(); - $scope.handleFocus(false); + var isDigestPass = true; + $scope.handleFocus(false, isDigestPass); } }); diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css index d8b464eeed1..e8edc0bb9fb 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css @@ -275,6 +275,10 @@ table.dataTable.table-condensed .sorting_desc:after { background: none !important; } +.paragraph-disable { + opacity : 0.6!important; +} + .ace_marker-layer .ace_selection { z-index: 0 !important; } @@ -327,12 +331,41 @@ table.dataTable.table-condensed .sorting_desc:after { .tableDisplay div { } -.tableDisplay img { +.tableDisplay img:not(.esri-bitmap) { display: block; max-width: 100%; height: auto; } +.esri-display-object > svg { + overflow: visible; +} + +.esri-popup > .esri-docked.esri-dock-to-bottom { + padding: 8px; + margin-top: 0px; +} + +.esri-popup-main { + max-height: 100%; +} + +span.map-offline-text { + display: table; + width: 100%; + height: 100%; + text-align: center; +} + +span.map-offline-text > span { + display: table-cell; + vertical-align: middle; + font-size: 18px; + font-weight: 700; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #212121; +} + .tableDisplay .btn-group span { margin: 10px 0 0 10px; font-size: 12px; @@ -350,7 +383,8 @@ table.dataTable.table-condensed .sorting_desc:after { } -.tableDisplay .option .columns { +.tableDisplay .option .columns, +div.esri-view { height: 100%; } diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.html b/zeppelin-web/src/app/notebook/paragraph/paragraph.html index 9b8f17d6c1b..de102e81a7d 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.html @@ -17,19 +17,20 @@
    -
    +
    @@ -37,17 +38,14 @@
    -
    + ng-class="{'paragraph-disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING', + 'paragraph-text--dirty' : dirtyText !== originalText && dirtyText !== undefined}">
    diff --git a/zeppelin-web/src/components/elasticInputCtrl/elasticInput.controller.js b/zeppelin-web/src/components/elasticInputCtrl/elasticInput.controller.js new file mode 100644 index 00000000000..ec19e93d90e --- /dev/null +++ b/zeppelin-web/src/components/elasticInputCtrl/elasticInput.controller.js @@ -0,0 +1,20 @@ +/* + * 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. + */ +'use strict'; + +angular.module('zeppelinWebApp') +.controller('ElasticInputCtrl', function() { + var vm = this; + vm.showEditor = false; +}); diff --git a/zeppelin-web/src/components/interpreter/interpreter.directive.js b/zeppelin-web/src/components/interpreter/interpreter.directive.js new file mode 100644 index 00000000000..1cf1ab2d178 --- /dev/null +++ b/zeppelin-web/src/components/interpreter/interpreter.directive.js @@ -0,0 +1,28 @@ +/* + * 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. + */ +'use strict'; + +angular.module('zeppelinWebApp').directive('interpreterDirective', function($timeout) { + return { + restrict: 'A', + link: function(scope, element, attr) { + if (scope.$last === true) { + $timeout(function() { + var id = 'ngRenderFinished'; + scope.$emit(id); + }); + } + } + }; +}); diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index c8da491e6df..2007322b5ed 100644 --- a/zeppelin-web/src/components/navbar/navbar.controller.js +++ b/zeppelin-web/src/components/navbar/navbar.controller.js @@ -18,29 +18,50 @@ angular.module('zeppelinWebApp') .controller('NavCtrl', function($scope, $rootScope, $http, $routeParams, $location, notebookListDataFactory, baseUrlSrv, websocketMsgSrv, arrayOrderingSrv, searchService) { + var vm = this; + vm.arrayOrderingSrv = arrayOrderingSrv; + vm.connected = websocketMsgSrv.isConnected(); + vm.isActive = isActive; + vm.logout = logout; + vm.notes = notebookListDataFactory; + vm.search = search; + vm.searchForm = searchService; + vm.showLoginWindow = showLoginWindow; + $scope.query = {q: ''}; - /** Current list of notes (ids) */ - $scope.showLoginWindow = function() { - setTimeout(function() { - angular.element('#userName').focus(); - }, 500); - }; + initController(); - var vm = this; - vm.notes = notebookListDataFactory; - vm.connected = websocketMsgSrv.isConnected(); - vm.websocketMsgSrv = websocketMsgSrv; - vm.arrayOrderingSrv = arrayOrderingSrv; - $scope.searchForm = searchService; + function getZeppelinVersion() { + $http.get(baseUrlSrv.getRestApiBase() + '/version').success( + function(data, status, headers, config) { + $rootScope.zeppelinVersion = data.body; + }).error( + function(data, status, headers, config) { + console.log('Error %o %o', status, data.message); + }); + } - angular.element('#notebook-list').perfectScrollbar({suppressScrollX: true}); + function initController() { + angular.element('#notebook-list').perfectScrollbar({suppressScrollX: true}); - angular.element(document).click(function() { - $scope.query.q = ''; - }); + angular.element(document).click(function() { + $scope.query.q = ''; + }); + + getZeppelinVersion(); + loadNotes(); + } + + function isActive(noteId) { + return ($routeParams.noteId === noteId); + } + + function loadNotes() { + websocketMsgSrv.getNotebookList(); + } - $scope.logout = function() { + function logout() { var logoutURL = baseUrlSrv.getRestApiBase() + '/login/logout'; //for firefox and safari @@ -60,42 +81,18 @@ angular.module('zeppelinWebApp') }, 1000); }); }); - }; - - $scope.search = function(searchTerm) { - $location.path('/search/' + searchTerm); - }; - - function loadNotes() { - websocketMsgSrv.getNotebookList(); } - function isActive(noteId) { - return ($routeParams.noteId === noteId); + function search(searchTerm) { + $location.path('/search/' + searchTerm); } - $rootScope.noteName = function(note) { - if (!_.isEmpty(note)) { - return arrayOrderingSrv.getNoteName(note); - } - }; - - function getZeppelinVersion() { - $http.get(baseUrlSrv.getRestApiBase() + '/version').success( - function(data, status, headers, config) { - $rootScope.zeppelinVersion = data.body; - }).error( - function(data, status, headers, config) { - console.log('Error %o %o', status, data.message); - }); + function showLoginWindow() { + setTimeout(function() { + angular.element('#userName').focus(); + }, 500); } - vm.loadNotes = loadNotes; - vm.isActive = isActive; - - getZeppelinVersion(); - vm.loadNotes(); - /* ** $scope.$on functions below */ diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index 6b9e7860972..109e328b455 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -45,15 +45,15 @@
  • Credential
  • Configuration
  • -
  • Logout
  • +
  • Logout
  • diff --git a/zeppelin-web/src/components/ngenter/ngenter.directive.js b/zeppelin-web/src/components/ngenter/ngenter.directive.js index f284c6977b3..89826c929ac 100644 --- a/zeppelin-web/src/components/ngenter/ngenter.directive.js +++ b/zeppelin-web/src/components/ngenter/ngenter.directive.js @@ -17,9 +17,11 @@ angular.module('zeppelinWebApp').directive('ngEnter', function() { return function(scope, element, attrs) { element.bind('keydown keypress', function(event) { if (event.which === 13) { - scope.$apply(function() { - scope.$eval(attrs.ngEnter); - }); + if (!event.shiftKey) { + scope.$apply(function() { + scope.$eval(attrs.ngEnter); + }); + } event.preventDefault(); } }); diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js index 76571374998..e4f45db0218 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js +++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js @@ -113,6 +113,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', $rootScope.$broadcast('listRevisionHistory', data); } else if (op === 'NOTE_REVISION') { $rootScope.$broadcast('noteRevision', data); + } else if (op === 'INTERPRETER_BINDINGS') { + $rootScope.$broadcast('interpreterBindings', data); } }); diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js index a4f7802d348..473299ec4d9 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js +++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js @@ -170,7 +170,7 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, }); }, - getNoteRevision: function(noteId, revisionId) { + getNoteByRevision: function(noteId, revisionId) { websocketEvents.sendNewEvent({ op: 'NOTE_REVISION', data: { @@ -196,6 +196,15 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, unsubscribeJobManager: function() { websocketEvents.sendNewEvent({op: 'UNSUBSCRIBE_JOBMANAGER'}); + }, + + getInterpreterBindings: function(noteID) { + websocketEvents.sendNewEvent({op: 'GET_INTERPRETER_BINDINGS', data: {noteID: noteID}}); + }, + + saveInterpreterBindings: function(noteID, selectedSettingIds) { + websocketEvents.sendNewEvent({op: 'SAVE_INTERPRETER_BINDINGS', + data: {noteID: noteID, selectedSettingIds: selectedSettingIds}}); } }; diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 9fe9489501c..9a8ae6fbb88 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -48,6 +48,7 @@ + @@ -144,6 +145,8 @@ + + @@ -162,6 +165,7 @@ + @@ -178,6 +182,7 @@ + diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js index 778f0f151fd..64e66c25bab 100644 --- a/zeppelin-web/test/karma.conf.js +++ b/zeppelin-web/test/karma.conf.js @@ -64,6 +64,8 @@ module.exports = function(config) { 'bower_components/pikaday/pikaday.js', 'bower_components/handsontable/dist/handsontable.js', 'bower_components/moment-duration-format/lib/moment-duration-format.js', + 'bower_components/select2/dist/js/select2.js', + 'bower_components/angular-esri-map/dist/angular-esri-map.js', 'bower_components/angular-mocks/angular-mocks.js', // endbower 'src/app/app.js', diff --git a/zeppelin-web/test/spec/controllers/notebook.js b/zeppelin-web/test/spec/controllers/notebook.js index d9b35b17b77..0b21cc090a6 100644 --- a/zeppelin-web/test/spec/controllers/notebook.js +++ b/zeppelin-web/test/spec/controllers/notebook.js @@ -7,7 +7,8 @@ describe('Controller: NotebookCtrl', function() { var websocketMsgSrvMock = { getNotebook: function() {}, - listRevisionHistory: function() {} + listRevisionHistory: function() {}, + getInterpreterBindings: function() {} }; var baseUrlSrvMock = { @@ -46,10 +47,6 @@ describe('Controller: NotebookCtrl', function() { }); }); - it('should set default value of "showEditor" to false', function() { - expect(scope.showEditor).toEqual(false); - }); - it('should set default value of "editorToggled" to false', function() { expect(scope.editorToggled).toEqual(false); }); diff --git a/zeppelin-web/test/spec/controllers/paragraph.js b/zeppelin-web/test/spec/controllers/paragraph.js index bb483f4b3c0..50691ba3591 100644 --- a/zeppelin-web/test/spec/controllers/paragraph.js +++ b/zeppelin-web/test/spec/controllers/paragraph.js @@ -39,7 +39,7 @@ describe('Controller: ParagraphCtrl', function() { 'getResultType', 'loadTableData', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange', 'removeGraphOptionKeys', 'removeGraphOptionValues', 'removeGraphOptionGroups', 'setGraphOptionValueAggr', 'removeScatterOptionXaxis', 'removeScatterOptionYaxis', 'removeScatterOptionGroup', - 'removeScatterOptionSize']; + 'removeScatterOptionSize', 'removeMapOptionLat', 'removeMapOptionLng', 'removeMapOptionPinInfo']; functions.forEach(function(fn) { it('check for scope functions to be defined : ' + fn, function() { diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index b7e1938ff60..2c56b4dc860 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -128,12 +128,6 @@ ${jetty.version} - - org.eclipse.jetty.websocket - websocket-client - ${jetty.version} - - org.quartz-scheduler quartz diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index 43fb6be02c0..864b149d379 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -272,10 +272,15 @@ public String getServerContextPath() { } public String getKeyStorePath() { - return getRelativeDir( - String.format("%s/%s", - getConfDir(), - getString(ConfVars.ZEPPELIN_SSL_KEYSTORE_PATH))); + String path = getString(ConfVars.ZEPPELIN_SSL_KEYSTORE_PATH); + if (path != null && path.startsWith("/") || isWindowsPath(path)) { + return path; + } else { + return getRelativeDir( + String.format("%s/%s", + getConfDir(), + getString(path))); + } } public String getKeyStoreType() { @@ -297,10 +302,13 @@ public String getKeyManagerPassword() { public String getTrustStorePath() { String path = getString(ConfVars.ZEPPELIN_SSL_TRUSTSTORE_PATH); - if (path == null) { - return getKeyStorePath(); + if (path != null && path.startsWith("/") || isWindowsPath(path)) { + return path; } else { - return getRelativeDir(path); + return getRelativeDir( + String.format("%s/%s", + getConfDir(), + getString(path))); } } @@ -427,10 +435,6 @@ public String getWebsocketMaxTextMessageSize() { return getString(ConfVars.ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE); } - public boolean getUseJdbcAlias() { - return getBoolean(ConfVars.ZEPPELIN_USE_JDBC_ALIAS); - } - public Map dumpConfigurations(ZeppelinConfiguration conf, ConfigurationKeyPredicate predicate) { Map configurations = new HashMap<>(); @@ -543,6 +547,7 @@ public static enum ConfVars { ZEPPELIN_NOTEBOOK_AZURE_SHARE("zeppelin.notebook.azure.share", "zeppelin"), ZEPPELIN_NOTEBOOK_AZURE_USER("zeppelin.notebook.azure.user", "user"), ZEPPELIN_NOTEBOOK_STORAGE("zeppelin.notebook.storage", VFSNotebookRepo.class.getName()), + ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC("zeppelin.notebook.one.way.sync", false), ZEPPELIN_INTERPRETER_REMOTE_RUNNER("zeppelin.interpreter.remoterunner", System.getProperty("os.name") .startsWith("Windows") ? "bin/interpreter.cmd" : "bin/interpreter.sh"), @@ -556,9 +561,7 @@ public static enum ConfVars { ZEPPELIN_ALLOWED_ORIGINS("zeppelin.server.allowed.origins", "*"), ZEPPELIN_ANONYMOUS_ALLOWED("zeppelin.anonymous.allowed", true), ZEPPELIN_CREDENTIALS_PERSIST("zeppelin.credentials.persist", true), - ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE("zeppelin.websocket.max.text.message.size", "1024000"), - ZEPPELIN_USE_JDBC_ALIAS("zeppelin.use.jdbc.alias", true); - + ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE("zeppelin.websocket.max.text.message.size", "1024000"); private String varName; @SuppressWarnings("rawtypes") diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java index 7f8699d566c..30b015368d3 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java @@ -55,6 +55,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.NullArgumentException; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.aether.RepositoryException; @@ -96,13 +97,13 @@ public class InterpreterFactory implements InterpreterGroupFactory { * This is only references with default settings, name and properties * key: InterpreterSetting.name */ - private Map interpreterSettingsRef = new HashMap<>(); + private final Map interpreterSettingsRef = new HashMap<>(); /** * This is used by creating and running Interpreters * key: InterpreterSetting.id <- This is becuase backward compatibility */ - private Map interpreterSettings = new HashMap<>(); + private final Map interpreterSettings = new HashMap<>(); private Map> interpreterBindings = new HashMap<>(); private List interpreterRepositories; @@ -174,7 +175,7 @@ public boolean accept(Path entry) throws IOException { registerInterpreterFromResource(cl, interpreterDirString, interpreterJson); - /** + /* * TODO(jongyoul) * - Remove these codes below because of legacy code * - Support ThreadInterpreter @@ -345,6 +346,8 @@ private void loadFromFile() throws IOException { InputStreamReader isr = new InputStreamReader(fis); BufferedReader bufferedReader = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); + InterpreterSetting interpreterSettingObject; + String depClassPath = StringUtils.EMPTY; String line; while ((line = bufferedReader.readLine()) != null) { sb.append(line); @@ -365,9 +368,14 @@ private void loadFromFile() throws IOException { setting.getOption().setRemote(true); // Update transient information from InterpreterSettingRef - // TODO(jl): Check if reference of setting is null - - setting.setPath(interpreterSettingsRef.get(setting.getGroup()).getPath()); + interpreterSettingObject = interpreterSettingsRef.get(setting.getGroup()); + if (interpreterSettingObject == null) { + logger.warn("can't get InterpreterSetting " + + "Information From loaded Interpreter Setting Ref - {} ", setting.getGroup()); + continue; + } + depClassPath = interpreterSettingObject.getPath(); + setting.setPath(depClassPath); setting.setInterpreterGroupFactory(this); loadInterpreterDependencies(setting); @@ -460,8 +468,6 @@ private void saveToFile() throws IOException { * Return ordered interpreter setting list. * The list does not contain more than one setting from the same interpreter class. * Order by InterpreterClass (order defined by ZEPPELIN_INTERPRETERS), Interpreter setting name - * - * @return */ public List getDefaultInterpreterSettingList() { // this list will contain default interpreter setting list @@ -500,12 +506,15 @@ private boolean findDefaultInterpreter(List infos) { public InterpreterSetting createNewSetting(String name, String group, List dependencies, InterpreterOption option, Properties p) throws IOException { + if (name.indexOf(".") >= 0) { + throw new IOException("'.' is invalid for InterpreterSetting name."); + } InterpreterSetting setting = createFromInterpreterSettingRef(group); setting.setName(name); setting.setGroup(group); setting.appendDependencies(dependencies); setting.setInterpreterOption(option); - setting.updateProperties(p); + setting.setProperties(p); setting.setInterpreterGroupFactory(this); interpreterSettings.put(setting.getId(), setting); saveToFile(); @@ -521,8 +530,7 @@ private InterpreterSetting add(String group, InterpreterInfo interpreterInfo, } /** - * @param group InterpreterSetting reference name - * @param properties + * @param group InterpreterSetting reference name * @return */ public InterpreterSetting add(String group, ArrayList interpreterInfos, @@ -571,8 +579,8 @@ public InterpreterSetting add(String group, ArrayList interpret } else { interpreterSetting = - new InterpreterSetting(group, null, interpreterInfos, properties, dependencies, - option, path); + new InterpreterSetting(group, null, interpreterInfos, properties, dependencies, option, + path); interpreterSettingsRef.put(group, interpreterSetting); } } @@ -837,7 +845,7 @@ public void setPropertyAndRestart(String id, InterpreterOption option, Propertie intpsetting.closeAndRmoveAllInterpreterGroups(); intpsetting.setOption(option); - intpsetting.updateProperties(properties); + intpsetting.setProperties(properties); intpsetting.setDependencies(dependencies); loadInterpreterDependencies(intpsetting); @@ -912,8 +920,6 @@ private Interpreter createRepl(String dirName, String className, Properties prop throws InterpreterException { logger.info("Create repl {} from {}", className, dirName); - updatePropertiesFromRegisteredInterpreter(property, className); - ClassLoader oldcl = Thread.currentThread().getContextClassLoader(); try { @@ -984,9 +990,6 @@ private Interpreter createRemoteRepl(String interpreterPath, String noteId, Stri String localRepoPath = conf.getInterpreterLocalRepoPath() + "/" + interpreterSettingId; int maxPoolSize = conf.getInt(ConfVars.ZEPPELIN_INTERPRETER_MAX_POOL_SIZE); - updatePropertiesFromRegisteredInterpreter(property, className); - - RemoteInterpreter remoteInterpreter = new RemoteInterpreter(property, noteId, className, conf.getInterpreterRemoteRunnerPath(), interpreterPath, localRepoPath, connectTimeout, maxPoolSize, @@ -996,22 +999,6 @@ private Interpreter createRemoteRepl(String interpreterPath, String noteId, Stri return new LazyOpenInterpreter(remoteInterpreter); } - private Properties updatePropertiesFromRegisteredInterpreter(Properties properties, - String className) { - RegisteredInterpreter registeredInterpreter = - Interpreter.findRegisteredInterpreterByClassName(className); - if (null != registeredInterpreter) { - Map defaultProperties = registeredInterpreter.getProperties(); - for (String key : defaultProperties.keySet()) { - if (!properties.containsKey(key) && null != defaultProperties.get(key).getValue()) { - properties.setProperty(key, defaultProperties.get(key).getValue()); - } - } - } - - return properties; - } - /** * map interpreter ids into noteId * @@ -1189,6 +1176,16 @@ public Interpreter getInterpreter(String noteId, String replName) { return interpreters.get(0); } } + + // Support the legacy way to use it + for (InterpreterSetting s : settings) { + if (s.getGroup().equals(replName)) { + List interpreters = createOrGetInterpreterList(noteId, s); + if (null != interpreters) { + return interpreters.get(0); + } + } + } } // dev interpreter diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java index 7aac7816c5e..2bcc4c69ac0 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java @@ -17,6 +17,8 @@ package org.apache.zeppelin.interpreter; +import java.util.List; + /** * */ @@ -28,7 +30,8 @@ public class InterpreterOption { boolean perNoteProcess; boolean isExistingProcess; - + boolean setPermission; + List users; public boolean isExistingProcess() { return isExistingProcess; @@ -46,6 +49,17 @@ public void setHost(String host) { this.host = host; } + public boolean permissionIsSet() { + return setPermission; + } + + public void setUserPermission(boolean setPermission) { + this.setPermission = setPermission; + } + + public List getUsers() { + return users; + } public InterpreterOption() { remote = false; diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java index 0288eb46f97..65f60cd1a6e 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java @@ -206,6 +206,10 @@ void updateProperties(Properties p) { this.properties.putAll(p); } + void setProperties(Properties p) { + this.properties = p; + } + void setGroup(String group) { this.group = group; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index 9106cf5f17c..1d75d1008a6 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -17,6 +17,8 @@ package org.apache.zeppelin.notebook; +import static java.lang.String.format; + import java.io.IOException; import java.io.Serializable; import java.util.HashMap; @@ -30,7 +32,6 @@ import java.util.concurrent.atomic.AtomicReference; import com.google.gson.Gson; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +57,11 @@ import org.apache.zeppelin.user.AuthenticationInfo; import org.apache.zeppelin.user.Credentials; +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.apache.commons.lang.StringUtils.isEmpty; +import static org.apache.commons.lang.StringUtils.isNotEmpty; +import static org.apache.commons.lang.StringUtils.isBlank; + /** * Binded interpreters for a note */ @@ -76,7 +82,7 @@ public class Note implements Serializable, ParagraphJobListener { private String name = ""; private String id; - private AtomicReference lastReplName = new AtomicReference<>(StringUtils.EMPTY); + private AtomicReference lastReplName = new AtomicReference<>(EMPTY); private transient ZeppelinConfiguration conf = ZeppelinConfiguration.create(); private Map> angularObjects = new HashMap<>(); @@ -122,7 +128,7 @@ private void generateId() { private String getDefaultInterpreterName() { InterpreterSetting setting = factory.getDefaultInterpreterSetting(getId()); - return null != setting ? setting.getName() : StringUtils.EMPTY; + return null != setting ? setting.getName() : EMPTY; } void putDefaultReplName() { @@ -131,10 +137,6 @@ void putDefaultReplName() { lastReplName.set(defaultInterpreterName); } - public String id() { - return id; - } - public String getId() { return id; } @@ -172,6 +174,28 @@ void setInterpreterFactory(InterpreterFactory factory) { } } + public void initializeJobListenerForParagraph(Paragraph paragraph) { + final Note paragraphNote = paragraph.getNote(); + if (paragraphNote.getId().equals(this.getId())) { + throw new IllegalArgumentException(format("The paragraph %s from note %s " + + "does not belong to note %s", paragraph.getId(), paragraphNote.getId(), + this.getId())); + } + + boolean foundParagraph = false; + for (Paragraph ownParagraph : paragraphs) { + if (paragraph.getId().equals(ownParagraph.getId())) { + paragraph.setListener(this.jobListenerFactory.getParagraphJobListener(this)); + foundParagraph = true; + } + } + + if (!foundParagraph) { + throw new IllegalArgumentException(format("Cannot find paragraph %s " + + "from note %s", paragraph.getId(), paragraphNote.getId())); + } + } + void setJobListenerFactory(JobListenerFactory jobListenerFactory) { this.jobListenerFactory = jobListenerFactory; } @@ -275,13 +299,17 @@ public Paragraph insertParagraph(int index) { */ private void addLastReplNameIfEmptyText(Paragraph p) { String replName = lastReplName.get(); - if (StringUtils.isEmpty(p.getText()) && StringUtils.isNotEmpty(replName)) { + if (isEmpty(p.getText()) && isNotEmpty(replName) && isBinding(replName)) { p.setText(getInterpreterName(replName) + " "); } } + public boolean isBinding(String replName) { + return factory.getInterpreter(this.getId(), replName) != null; + } + private String getInterpreterName(String replName) { - return StringUtils.isBlank(replName) ? StringUtils.EMPTY : "%" + replName; + return isBlank(replName) ? EMPTY : "%" + replName; } /** @@ -292,7 +320,7 @@ private String getInterpreterName(String replName) { */ public Paragraph removeParagraph(String paragraphId) { removeAllAngularObjectInParagraph(paragraphId); - ResourcePoolUtils.removeResourcesBelongsToParagraph(id(), paragraphId); + ResourcePoolUtils.removeResourcesBelongsToParagraph(getId(), paragraphId); synchronized (paragraphs) { Iterator i = paragraphs.iterator(); while (i.hasNext()) { @@ -354,8 +382,8 @@ public void moveParagraph(String paragraphId, int index, boolean throwWhenIndexI if (index < 0 || index >= paragraphs.size()) { if (throwWhenIndexIsOutOfBound) { - throw new IndexOutOfBoundsException("paragraph size is " + paragraphs.size() + - " , index is " + index); + throw new IndexOutOfBoundsException( + "paragraph size is " + paragraphs.size() + " , index is " + index); } else { return; } @@ -411,24 +439,40 @@ public List> generateParagraphsInfo() { List> paragraphsInfo = new LinkedList<>(); synchronized (paragraphs) { for (Paragraph p : paragraphs) { - Map info = new HashMap<>(); - info.put("id", p.getId()); - info.put("status", p.getStatus().toString()); - if (p.getDateStarted() != null) { - info.put("started", p.getDateStarted().toString()); - } - if (p.getDateFinished() != null) { - info.put("finished", p.getDateFinished().toString()); - } - if (p.getStatus().isRunning()) { - info.put("progress", String.valueOf(p.progress())); - } + Map info = populatePragraphInfo(p); paragraphsInfo.add(info); } } return paragraphsInfo; } + public Map generateSingleParagraphInfo(String paragraphId) { + synchronized (paragraphs) { + for (Paragraph p : paragraphs) { + if (p.getId().equals(paragraphId)) { + return populatePragraphInfo(p); + } + } + return new HashMap<>(); + } + } + + private Map populatePragraphInfo(Paragraph p) { + Map info = new HashMap<>(); + info.put("id", p.getId()); + info.put("status", p.getStatus().toString()); + if (p.getDateStarted() != null) { + info.put("started", p.getDateStarted().toString()); + } + if (p.getDateFinished() != null) { + info.put("finished", p.getDateFinished().toString()); + } + if (p.getStatus().isRunning()) { + info.put("progress", String.valueOf(p.progress())); + } + return info; + } + /** * Run all paragraphs sequentially. */ @@ -445,11 +489,7 @@ public void runAll() { AuthenticationInfo authenticationInfo = new AuthenticationInfo(); authenticationInfo.setUser(cronExecutingUser); p.setAuthenticationInfo(authenticationInfo); - - p.setListener(jobListenerFactory.getParagraphJobListener(this)); - Interpreter intp = factory.getInterpreter(getId(), p.getRequiredReplName()); - - intp.getScheduler().submit(p); + run(p.getId()); } } } @@ -464,16 +504,15 @@ public void run(String paragraphId) { p.setListener(jobListenerFactory.getParagraphJobListener(this)); String requiredReplName = p.getRequiredReplName(); Interpreter intp = factory.getInterpreter(getId(), requiredReplName); - if (intp == null) { - // TODO(jongyoul): Make "%jdbc" configurable from JdbcInterpreter - if (conf.getUseJdbcAlias() && null != (intp = factory.getInterpreter(getId(), "jdbc"))) { - String pText = p.getText().replaceFirst(requiredReplName, "jdbc(" + requiredReplName + ")"); - logger.debug("New paragraph: {}", pText); - p.setEffectiveText(pText); - } else { - throw new InterpreterException("Interpreter " + requiredReplName + " not found"); - } + String intpExceptionMsg = + p.getJobName() + "'s Interpreter " + requiredReplName + " not found"; + InterpreterException intpException = new InterpreterException(intpExceptionMsg); + InterpreterResult intpResult = + new InterpreterResult(InterpreterResult.Code.ERROR, intpException.getMessage()); + p.setReturn(intpResult, intpException); + p.setStatus(Job.Status.ERROR); + throw intpException; } if (p.getConfig().get("enabled") == null || (Boolean) p.getConfig().get("enabled")) { intp.getScheduler().submit(p); @@ -569,7 +608,7 @@ public void persist(AuthenticationInfo subject) throws IOException { } private void setLastReplName(Paragraph lastParagraphStarted) { - if (StringUtils.isNotEmpty(lastParagraphStarted.getRequiredReplName())) { + if (isNotEmpty(lastParagraphStarted.getRequiredReplName())) { lastReplName.set(lastParagraphStarted.getRequiredReplName()); } } @@ -586,7 +625,7 @@ public void persist(int maxDelaySec, AuthenticationInfo subject) { } void unpersist(AuthenticationInfo subject) throws IOException { - repo.remove(id(), subject); + repo.remove(getId(), subject); } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java index f75a107e36d..9783c7609d5 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java @@ -36,7 +36,7 @@ public NoteInfo(String id, String name, Map config) { } public NoteInfo(Note note) { - id = note.id(); + id = note.getId(); name = note.getName(); config = note.getConfig(); } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java index 3620464b33b..9acb156a3a3 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java @@ -151,10 +151,10 @@ public Note createNote(List interpreterIds, AuthenticationInfo subject) Note note = new Note(notebookRepo, replFactory, jobListenerFactory, notebookIndex, credentials, this); synchronized (notes) { - notes.put(note.id(), note); + notes.put(note.getId(), note); } if (interpreterIds != null) { - bindInterpretersToNote(note.id(), interpreterIds); + bindInterpretersToNote(note.getId(), interpreterIds); note.putDefaultReplName(); } @@ -239,10 +239,12 @@ public Note cloneNote(String sourceNoteID, String newNoteName, AuthenticationInf Note newNote = createNote(subject); if (newNoteName != null) { newNote.setName(newNoteName); + } else { + newNote.setName("Note " + newNote.getId()); } // Copy the interpreter bindings - List boundInterpreterSettingsIds = getBindedInterpreterSettingsIds(sourceNote.id()); - bindInterpretersToNote(newNote.id(), boundInterpreterSettingsIds); + List boundInterpreterSettingsIds = getBindedInterpreterSettingsIds(sourceNote.getId()); + bindInterpretersToNote(newNote.getId(), boundInterpreterSettingsIds); List paragraphs = sourceNote.getParagraphs(); for (Paragraph p : paragraphs) { @@ -358,14 +360,14 @@ public Revision checkpointNote(String noteId, String checkpointMessage, return notebookRepo.checkpoint(noteId, checkpointMessage, subject); } - public List listRevisionHistory(String noteId, + public List listRevisionHistory(String noteId, AuthenticationInfo subject) { return notebookRepo.revisionHistory(noteId, subject); } - public Note getNoteRevision(String noteId, Revision revision, AuthenticationInfo subject) + public Note getNoteByRevision(String noteId, String revisionId, AuthenticationInfo subject) throws IOException { - return notebookRepo.get(noteId, revision, subject); + return notebookRepo.get(noteId, revisionId, subject); } @SuppressWarnings("rawtypes") @@ -419,15 +421,15 @@ private Note loadNoteFromRepo(String id, AuthenticationInfo subject) { note.setNoteEventListener(this); synchronized (notes) { - notes.put(note.id(), note); - refreshCron(note.id()); + notes.put(note.getId(), note); + refreshCron(note.getId()); } for (String name : angularObjectSnapshot.keySet()) { SnapshotAngularObject snapshot = angularObjectSnapshot.get(name); List settings = replFactory.get(); for (InterpreterSetting setting : settings) { - InterpreterGroup intpGroup = setting.getInterpreterGroup(note.id()); + InterpreterGroup intpGroup = setting.getInterpreterGroup(note.getId()); if (intpGroup.getId().equals(snapshot.getIntpGroupId())) { AngularObjectRegistry registry = intpGroup.getAngularObjectRegistry(); String noteId = snapshot.getAngularObject().getNoteId(); @@ -508,11 +510,11 @@ public List getAllNotes() { Collections.sort(noteList, new Comparator() { @Override public int compare(Note note1, Note note2) { - String name1 = note1.id(); + String name1 = note1.getId(); if (note1.getName() != null) { name1 = note1.getName(); } - String name2 = note2.id(); + String name2 = note2.getId(); if (note2.getName() != null) { name2 = note2.getName(); } @@ -593,14 +595,14 @@ public List> getJobListforNotebook(boolean needsReload, Map info = new HashMap<>(); // set notebook ID - info.put("notebookId", note.id()); + info.put("notebookId", note.getId()); // set notebook Name String notebookName = note.getName(); if (notebookName != null && !notebookName.equals("")) { info.put("notebookName", note.getName()); } else { - info.put("notebookName", "Note " + note.id()); + info.put("notebookName", "Note " + note.getId()); } // set notebook type ( cron or normal ) diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java index 308cbb05047..f326ebaf454 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java @@ -53,7 +53,6 @@ public class Paragraph extends Job implements Serializable, Cloneable { private transient InterpreterFactory factory; private transient Note note; private transient AuthenticationInfo authenticationInfo; - private transient String effectiveText; String title; String text; @@ -114,14 +113,6 @@ public void setText(String newText) { this.dateUpdated = new Date(); } - public void setEffectiveText(String effectiveText) { - this.effectiveText = effectiveText; - } - - public String getEffectiveText() { - return effectiveText; - } - public AuthenticationInfo getAuthenticationInfo() { return authenticationInfo; } @@ -153,7 +144,7 @@ public boolean isEnabled() { } public String getRequiredReplName() { - return getRequiredReplName(null != effectiveText ? effectiveText : text); + return getRequiredReplName(text); } public static String getRequiredReplName(String text) { @@ -182,7 +173,7 @@ public static String getRequiredReplName(String text) { } public String getScriptBody() { - return getScriptBody(null != effectiveText ? effectiveText : text); + return getScriptBody(text); } public static String getScriptBody(String text) { @@ -275,6 +266,19 @@ public Map info() { return null; } + private boolean hasPermission(String user, List intpUsers) { + if (1 > intpUsers.size()) { + return true; + } + + for (String u: intpUsers) { + if (user.trim().equals(u.trim())) { + return true; + } + } + return false; + } + @Override protected Object jobRun() throws Throwable { String replName = getRequiredReplName(); @@ -285,6 +289,17 @@ protected Object jobRun() throws Throwable { throw new RuntimeException("Can not find interpreter for " + getRequiredReplName()); } + if (this.noteHasUser() && this.noteHasInterpreters()) { + InterpreterSetting intp = getInterpreterSettingById(repl.getInterpreterGroup().getId()); + if (intp != null && + interpreterHasUser(intp) && + isUserAuthorizedToAccessInterpreter(intp.getOption()) == false) { + logger.error("{} has no permission for {} ", authenticationInfo.getUser(), repl); + return new InterpreterResult(Code.ERROR, authenticationInfo.getUser() + + " has no permission for " + getRequiredReplName()); + } + } + String script = getScriptBody(); // inject form if (repl.getFormType() == FormType.NATIVE) { @@ -335,10 +350,37 @@ protected Object jobRun() throws Throwable { } } finally { InterpreterContext.remove(); - effectiveText = null; } } + private boolean noteHasUser() { + return this.user != null; + } + + private boolean noteHasInterpreters() { + return !factory.getInterpreterSettings(note.getId()).isEmpty(); + } + + private boolean interpreterHasUser(InterpreterSetting intp) { + return intp.getOption().permissionIsSet() && intp.getOption().getUsers() != null; + } + + private boolean isUserAuthorizedToAccessInterpreter(InterpreterOption intpOpt){ + return intpOpt.permissionIsSet() && + hasPermission(authenticationInfo.getUser(), intpOpt.getUsers()); + } + + private InterpreterSetting getInterpreterSettingById(String id) { + InterpreterSetting setting = null; + for (InterpreterSetting i: factory.getInterpreterSettings(note.getId())) { + if (id.startsWith(i.getId())) { + setting = i; + break; + } + } + return setting; + } + @Override protected boolean jobAbort() { Interpreter repl = getRepl(getRequiredReplName()); @@ -399,13 +441,13 @@ private InterpreterContext getInterpreterContext(InterpreterOutput output) { if (!factory.getInterpreterSettings(note.getId()).isEmpty()) { InterpreterSetting intpGroup = factory.getInterpreterSettings(note.getId()).get(0); - registry = intpGroup.getInterpreterGroup(note.id()).getAngularObjectRegistry(); - resourcePool = intpGroup.getInterpreterGroup(note.id()).getResourcePool(); + registry = intpGroup.getInterpreterGroup(note.getId()).getAngularObjectRegistry(); + resourcePool = intpGroup.getInterpreterGroup(note.getId()).getResourcePool(); } List runners = new LinkedList(); for (Paragraph p : note.getParagraphs()) { - runners.add(new ParagraphRunner(note, note.id(), p.getId())); + runners.add(new ParagraphRunner(note, note.getId(), p.getId())); } final Paragraph self = this; @@ -418,7 +460,7 @@ private InterpreterContext getInterpreterContext(InterpreterOutput output) { } InterpreterContext interpreterContext = new InterpreterContext( - note.id(), + note.getId(), getId(), this.getTitle(), this.getText(), diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java index be6fbb62da3..ca72fc1f7f0 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java @@ -217,7 +217,7 @@ public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationIn } @Override - public Note get(String noteId, Revision rev, AuthenticationInfo subject) throws IOException { + public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException { // Auto-generated method stub return null; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java index 47059802669..b0678dd4795 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java @@ -108,7 +108,7 @@ public Revision checkpoint(String pattern, String commitMessage, AuthenticationI * 4. apply stash on top and remove it */ @Override - public synchronized Note get(String noteId, Revision rev, AuthenticationInfo subject) + public synchronized Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException { Note note = null; RevCommit stash = null; @@ -123,7 +123,7 @@ public synchronized Note get(String noteId, Revision rev, AuthenticationInfo sub } ObjectId head = git.getRepository().resolve(Constants.HEAD); // checkout to target revision - git.checkout().setStartPoint(rev.id).addPath(noteId).call(); + git.checkout().setStartPoint(revId).addPath(noteId).call(); // get the note note = super.get(noteId, subject); // checkout back to head @@ -137,7 +137,7 @@ public synchronized Note get(String noteId, Revision rev, AuthenticationInfo sub stashes.size()); } } catch (GitAPIException e) { - LOG.error("Failed to return note from revision \"{}\"", rev.message, e); + LOG.error("Failed to return note from revision \"{}\"", revId, e); } return note; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java index 85df81f5adc..aff684fc158 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java @@ -89,7 +89,7 @@ public interface NotebookRepo { * @return a Notebook * @throws IOException */ - @ZeppelinApi public Note get(String noteId, Revision rev, AuthenticationInfo subject) + @ZeppelinApi public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException; /** diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java index 6c499c6e312..72087262aca 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java @@ -44,11 +44,13 @@ public class NotebookRepoSync implements NotebookRepo { private static final int maxRepoNum = 2; private static final String pushKey = "pushNoteIDs"; private static final String pullKey = "pullNoteIDs"; + private static final String delDstKey = "delDstNoteIDs"; private static ZeppelinConfiguration config; private static final String defaultStorage = "org.apache.zeppelin.notebook.repo.VFSNotebookRepo"; private List repos = new ArrayList(); + private final boolean oneWaySync; /** * @param noteIndex @@ -58,6 +60,7 @@ public class NotebookRepoSync implements NotebookRepo { @SuppressWarnings("static-access") public NotebookRepoSync(ZeppelinConfiguration conf) { config = conf; + oneWaySync = conf.getBoolean(ConfVars.ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC); String allStorageClassNames = conf.getString(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE).trim(); if (allStorageClassNames.isEmpty()) { allStorageClassNames = defaultStorage; @@ -182,6 +185,8 @@ void sync(int sourceRepoIndex, int destRepoIndex) throws IOException { Map> noteIDs = notesCheckDiff(srcNotes, srcRepo, dstNotes, dstRepo); List pushNoteIDs = noteIDs.get(pushKey); List pullNoteIDs = noteIDs.get(pullKey); + List delDstNoteIDs = noteIDs.get(delDstKey); + if (!pushNoteIDs.isEmpty()) { LOG.info("Notes with the following IDs will be pushed"); for (String id : pushNoteIDs) { @@ -202,6 +207,16 @@ void sync(int sourceRepoIndex, int destRepoIndex) throws IOException { LOG.info("Nothing to pull"); } + if (!delDstNoteIDs.isEmpty()) { + LOG.info("Notes with the following IDs will be deleted from dest"); + for (String id : delDstNoteIDs) { + LOG.info("ID : " + id); + } + deleteNotes(delDstNoteIDs, dstRepo); + } else { + LOG.info("Nothing to delete from dest"); + } + LOG.info("Sync ended"); } @@ -216,6 +231,12 @@ private void pushNotes(List ids, NotebookRepo localRepo, } } + private void deleteNotes(List ids, NotebookRepo repo) throws IOException { + for (String id : ids) { + repo.remove(id, null); + } + } + public int getRepoCount() { return repos.size(); } @@ -237,6 +258,7 @@ private Map> notesCheckDiff(List sourceNotes, throws IOException { List pushIDs = new ArrayList(); List pullIDs = new ArrayList(); + List delDstIDs = new ArrayList(); NoteInfo dnote; Date sdate, ddate; @@ -246,14 +268,18 @@ private Map> notesCheckDiff(List sourceNotes, /* note exists in source and destination storage systems */ sdate = lastModificationDate(sourceRepo.get(snote.getId(), null)); ddate = lastModificationDate(destRepo.get(dnote.getId(), null)); - if (sdate.after(ddate)) { - /* source contains more up to date note - push */ - pushIDs.add(snote.getId()); - LOG.info("Modified note is added to push list : " + sdate); - } else if (sdate.compareTo(ddate) != 0) { - /* destination contains more up to date note - pull */ - LOG.info("Modified note is added to pull list : " + ddate); - pullIDs.add(snote.getId()); + + if (sdate.compareTo(ddate) != 0) { + if (sdate.after(ddate) || oneWaySync) { + /* if source contains more up to date note - push + * if oneWaySync is enabled, always push no matter who's newer */ + pushIDs.add(snote.getId()); + LOG.info("Modified note is added to push list : " + sdate); + } else { + /* destination contains more up to date note - pull */ + LOG.info("Modified note is added to pull list : " + ddate); + pullIDs.add(snote.getId()); + } } } else { /* note exists in source storage, and absent in destination @@ -266,14 +292,23 @@ private Map> notesCheckDiff(List sourceNotes, for (NoteInfo note : destNotes) { dnote = containsID(sourceNotes, note.getId()); if (dnote == null) { - /* note exists in destination storage, and absent in source - pull*/ - pullIDs.add(note.getId()); + /* note exists in destination storage, and absent in source */ + if (oneWaySync) { + /* if oneWaySync is enabled, delete the note from destination */ + LOG.info("Extraneous note is added to delete dest list : " + note.getId()); + delDstIDs.add(note.getId()); + } else { + /* if oneWaySync is disabled, pull the note from destination */ + LOG.info("Missing note is added to pull list : " + note.getId()); + pullIDs.add(note.getId()); + } } } Map> map = new HashMap>(); map.put(pushKey, pushIDs); map.put(pullKey, pullIDs); + map.put(delDstKey, delDstIDs); return map; } @@ -355,12 +390,12 @@ public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationIn } @Override - public Note get(String noteId, Revision rev, AuthenticationInfo subject) { + public Note get(String noteId, String revId, AuthenticationInfo subject) { Note revisionNote = null; try { - revisionNote = getRepo(0).get(noteId, rev, subject); + revisionNote = getRepo(0).get(noteId, revId, subject); } catch (IOException e) { - LOG.error("Failed to get revision {} of note {}", rev.id, noteId, e); + LOG.error("Failed to get revision {} of note {}", revId, noteId, e); } return revisionNote; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java index 460c9931e69..0163fc4b859 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java @@ -210,7 +210,7 @@ public void save(Note note, AuthenticationInfo subject) throws IOException { gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); String json = gson.toJson(note); - String key = user + "/" + "notebook" + "/" + note.id() + "/" + "note.json"; + String key = user + "/" + "notebook" + "/" + note.getId() + "/" + "note.json"; File file = File.createTempFile("note", "json"); try { @@ -260,7 +260,7 @@ public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationIn } @Override - public Note get(String noteId, Revision rev, AuthenticationInfo subject) throws IOException { + public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException { // Auto-generated method stub return null; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java index 430a06990d3..213fdf87e95 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java @@ -226,7 +226,7 @@ public synchronized void save(Note note, AuthenticationInfo subject) throws IOEx FileObject rootDir = getRootDir(); - FileObject noteDir = rootDir.resolveFile(note.id(), NameScope.CHILD); + FileObject noteDir = rootDir.resolveFile(note.getId(), NameScope.CHILD); if (!noteDir.exists()) { noteDir.createFolder(); @@ -274,7 +274,7 @@ public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationIn } @Override - public Note get(String noteId, Revision rev, AuthenticationInfo subject) throws IOException { + public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException { // Auto-generated method stub return null; } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java index d2f6ca2f755..d1864c5c7dc 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java @@ -33,6 +33,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -40,7 +42,7 @@ * ZeppelinHub repo class. */ public class ZeppelinHubRepo implements NotebookRepo { - private static final Logger LOG = LoggerFactory.getLogger(ZeppelinhubRestApiHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(ZeppelinHubRepo.class); private static final String DEFAULT_SERVER = "https://www.zeppelinhub.com"; static final String ZEPPELIN_CONF_PROP_NAME_SERVER = "zeppelinhub.api.address"; static final String ZEPPELIN_CONF_PROP_NAME_TOKEN = "zeppelinhub.api.token"; @@ -174,7 +176,7 @@ public void save(Note note, AuthenticationInfo subject) throws IOException { } String notebook = GSON.toJson(note); restApiClient.asyncPut(notebook); - LOG.info("ZeppelinHub REST API saving note {} ", note.id()); + LOG.info("ZeppelinHub REST API saving note {} ", note.getId()); } @Override @@ -191,20 +193,45 @@ public void close() { @Override public Revision checkpoint(String noteId, String checkpointMsg, AuthenticationInfo subject) throws IOException { - // Auto-generated method stub - return null; + if (StringUtils.isBlank(noteId)) { + return null; + } + String endpoint = Joiner.on("/").join(noteId, "checkpoint"); + String content = GSON.toJson(ImmutableMap.of("message", checkpointMsg)); + String response = restApiClient.asyncPutWithResponseBody(endpoint, content); + + return GSON.fromJson(response, Revision.class); } @Override - public Note get(String noteId, Revision rev, AuthenticationInfo subject) throws IOException { - // Auto-generated method stub - return null; + public Note get(String noteId, String revId, AuthenticationInfo subject) throws IOException { + if (StringUtils.isBlank(noteId) || StringUtils.isBlank(revId)) { + return EMPTY_NOTE; + } + String endpoint = Joiner.on("/").join(noteId, "checkpoint", revId); + String response = restApiClient.asyncGet(endpoint); + Note note = GSON.fromJson(response, Note.class); + if (note == null) { + return EMPTY_NOTE; + } + LOG.info("ZeppelinHub REST API get note {} revision {}", noteId, revId); + return note; } @Override public List revisionHistory(String noteId, AuthenticationInfo subject) { - // Auto-generated method stub - return null; + if (StringUtils.isBlank(noteId)) { + return Collections.emptyList(); + } + String endpoint = Joiner.on("/").join(noteId, "checkpoint"); + List history = Collections.emptyList(); + try { + String response = restApiClient.asyncGet(endpoint); + history = GSON.fromJson(response, new TypeToken>(){}.getType()); + } catch (IOException e) { + LOG.error("Cannot get note history", e); + } + return history; } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/rest/ZeppelinhubRestApiHandler.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/rest/ZeppelinhubRestApiHandler.java index 8f9b2e5983c..82159fc68ec 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/rest/ZeppelinhubRestApiHandler.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/rest/ZeppelinhubRestApiHandler.java @@ -25,9 +25,8 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpMethod; @@ -115,89 +114,66 @@ private HttpClient getAsyncClient() { } public String asyncGet(String argument) throws IOException { - String note = StringUtils.EMPTY; + return sendToZeppelinHub(HttpMethod.GET, zepelinhubUrl + argument); + } + + public String asyncPutWithResponseBody(String url, String json) throws IOException { + if (StringUtils.isBlank(url) || StringUtils.isBlank(json)) { + LOG.error("Empty note, cannot send it to zeppelinHub"); + throw new IOException("Cannot send emtpy note to zeppelinHub"); + } + return sendToZeppelinHub(HttpMethod.PUT, zepelinhubUrl + url, json); + } + + public void asyncPut(String jsonNote) throws IOException { + if (StringUtils.isBlank(jsonNote)) { + LOG.error("Cannot save empty note/string to ZeppelinHub"); + return; + } + sendToZeppelinHub(HttpMethod.PUT, zepelinhubUrl, jsonNote); + } + public void asyncDel(String argument) throws IOException { + if (StringUtils.isBlank(argument)) { + LOG.error("Cannot delete empty note from ZeppelinHub"); + return; + } + sendToZeppelinHub(HttpMethod.DELETE, zepelinhubUrl + argument); + } + + private String sendToZeppelinHub(HttpMethod method, String url) throws IOException { + return sendToZeppelinHub(method, url, StringUtils.EMPTY); + } + + private String sendToZeppelinHub(HttpMethod method, String url, String json) throws IOException { InputStreamResponseListener listener = new InputStreamResponseListener(); - client.newRequest(zepelinhubUrl + argument) - .header(ZEPPELIN_TOKEN_HEADER, token) - .send(listener); - - // Wait for the response headers to arrive Response response; + String data; + + Request request = client.newRequest(url).method(method).header(ZEPPELIN_TOKEN_HEADER, token); + if ((method.equals(HttpMethod.PUT) || method.equals(HttpMethod.POST)) && + !StringUtils.isBlank(json)) { + request.content(new StringContentProvider(json, "UTF-8"), "application/json;charset=UTF-8"); + } + request.send(listener); + try { response = listener.get(30, TimeUnit.SECONDS); } catch (InterruptedException | TimeoutException | ExecutionException e) { - LOG.error("Cannot perform Get request to ZeppelinHub", e); - throw new IOException("Cannot load note from ZeppelinHub", e); + LOG.error("Cannot perform {} request to ZeppelinHub", method, e); + throw new IOException("Cannot perform " + method + " request to ZeppelinHub", e); } int code = response.getStatus(); if (code == 200) { try (InputStream responseContent = listener.getInputStream()) { - note = IOUtils.toString(responseContent, "UTF-8"); + data = IOUtils.toString(responseContent, "UTF-8"); } } else { - LOG.error("ZeppelinHub Get {} returned with status {} ", zepelinhubUrl + argument, code); - throw new IOException("Cannot load note from ZeppelinHub"); - } - return note; - } - - public void asyncPut(String jsonNote) throws IOException { - if (StringUtils.isBlank(jsonNote)) { - LOG.error("Cannot save empty note/string to ZeppelinHub"); - return; - } - - client.newRequest(zepelinhubUrl).method(HttpMethod.PUT) - .header(ZEPPELIN_TOKEN_HEADER, token) - .content(new StringContentProvider(jsonNote, "UTF-8"), "application/json;charset=UTF-8") - .send(new BufferingResponseListener() { - - @Override - public void onComplete(Result res) { - if (!res.isFailed() && res.getResponse().getStatus() == 200) { - LOG.info("Successfully saved note to ZeppelinHub with {}", - res.getResponse().getStatus()); - } else { - LOG.warn("Failed to save note to ZeppelinHub with HttpStatus {}", - res.getResponse().getStatus()); - } - } - - @Override - public void onFailure(Response response, Throwable failure) { - LOG.error("Failed to save note to ZeppelinHub: {}", response.getReason(), failure); - } - }); - } - - public void asyncDel(String argument) { - if (StringUtils.isBlank(argument)) { - LOG.error("Cannot delete empty note from ZeppelinHub"); - return; + LOG.error("ZeppelinHub {} {} returned with status {} ", method, url, code); + throw new IOException("Cannot perform " + method + " request to ZeppelinHub"); } - client.newRequest(zepelinhubUrl + argument) - .method(HttpMethod.DELETE) - .header(ZEPPELIN_TOKEN_HEADER, token) - .send(new BufferingResponseListener() { - - @Override - public void onComplete(Result res) { - if (!res.isFailed() && res.getResponse().getStatus() == 200) { - LOG.info("Successfully removed note from ZeppelinHub with {}", - res.getResponse().getStatus()); - } else { - LOG.warn("Failed to remove note from ZeppelinHub with HttpStatus {}", - res.getResponse().getStatus()); - } - } - - @Override - public void onFailure(Response response, Throwable failure) { - LOG.error("Failed to remove note from ZeppelinHub: {}", response.getReason(), failure); - } - }); + return data; } public void close() { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java index e2f5fe9186f..e91dfbb99e7 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java @@ -127,8 +127,14 @@ public static enum OP { APP_STATUS_CHANGE, // [s-c] on app status change LIST_NOTEBOOK_JOBS, // [c-s] get notebook job management infomations - LIST_UPDATE_NOTEBOOK_JOBS // [c-s] get job management informations for until unixtime + LIST_UPDATE_NOTEBOOK_JOBS, // [c-s] get job management informations for until unixtime // @param unixTime + GET_INTERPRETER_BINDINGS, // [c-s] get interpreter bindings + // @param noteID + SAVE_INTERPRETER_BINDINGS, // [c-s] save interpreter bindings + // @param noteID + // @param selectedSettingIds + INTERPRETER_BINDINGS // [s-c] interpreter bindings } public OP op; diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java index 0af6aca7fbe..b32b3d8feb2 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java @@ -176,7 +176,7 @@ public void testUnloadOnParagraphRemove() throws IOException { new String[][]{}); Note note1 = notebook.createNote(null); - factory.setInterpreters(note1.id(), factory.getDefaultInterpreterSettingList()); + factory.setInterpreters(note1.getId(), factory.getDefaultInterpreterSettingList()); Paragraph p1 = note1.addParagraph(); @@ -214,7 +214,7 @@ public void testUnloadOnInterpreterUnbind() throws IOException { new String[][]{}); Note note1 = notebook.createNote(null); - notebook.bindInterpretersToNote(note1.id(), factory.getDefaultInterpreterSettingList()); + notebook.bindInterpretersToNote(note1.getId(), factory.getDefaultInterpreterSettingList()); Paragraph p1 = note1.addParagraph(); @@ -231,7 +231,7 @@ public void testUnloadOnInterpreterUnbind() throws IOException { } // when unbind interpreter - notebook.bindInterpretersToNote(note1.id(), new LinkedList()); + notebook.bindInterpretersToNote(note1.getId(), new LinkedList()); // then assertEquals(ApplicationState.Status.UNLOADED, app.getStatus()); @@ -255,7 +255,7 @@ public void testInterpreterUnbindOfNullReplParagraph() throws IOException { // Unbind all interpreter from note // NullPointerException shouldn't occur here - notebook.bindInterpretersToNote(note1.id(), new LinkedList()); + notebook.bindInterpretersToNote(note1.getId(), new LinkedList()); // remove note notebook.removeNote(note1.getId(), null); @@ -273,9 +273,9 @@ public void testUnloadOnInterpreterRestart() throws IOException { new String[][]{}); Note note1 = notebook.createNote(null); - notebook.bindInterpretersToNote(note1.id(), factory.getDefaultInterpreterSettingList()); + notebook.bindInterpretersToNote(note1.getId(), factory.getDefaultInterpreterSettingList()); String mock1IntpSettingId = null; - for (InterpreterSetting setting : notebook.getBindedInterpreterSettings(note1.id())) { + for (InterpreterSetting setting : notebook.getBindedInterpreterSettings(note1.getId())) { if (setting.getName().equals("mock1")) { mock1IntpSettingId = setting.getId(); break; diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java index e28416491ee..5e44d62c05a 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/interpreter/InterpreterFactoryTest.java @@ -155,5 +155,16 @@ public void testInterpreterAliases() throws IOException, RepositoryException { }}); assertEquals("className1", factory.getInterpreter("note", "test-group1").getClassName()); + assertEquals("className1", factory.getInterpreter("note", "group1").getClassName()); + } + + @Test + public void testInvalidInterpreterSettingName() { + try { + factory.createNewSetting("new.mock1", "mock1", new LinkedList(), new InterpreterOption(false), new Properties()); + fail("expect fail because of invalid InterpreterSetting Name"); + } catch (IOException e) { + assertEquals("'.' is invalid for InterpreterSetting name.", e.getMessage()); + } } } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java index cff66adc78d..4917ac43cba 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java @@ -23,6 +23,7 @@ import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.interpreter.InterpreterSetting; +import org.apache.zeppelin.interpreter.mock.MockInterpreter2; import org.apache.zeppelin.notebook.repo.NotebookRepo; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.search.SearchService; @@ -82,27 +83,6 @@ public void runNormalTest() { assertEquals("Paragraph text", pText, pCaptor.getValue().getText()); } - @Test - public void runJdbcTest() { - when(interpreterFactory.getInterpreter(anyString(), eq("mysql"))).thenReturn(null); - when(interpreterFactory.getInterpreter(anyString(), eq("jdbc"))).thenReturn(interpreter); - when(interpreter.getScheduler()).thenReturn(scheduler); - - String pText = "%mysql show databases"; - - Note note = new Note(repo, interpreterFactory, jobListenerFactory, index, credentials, noteEventListener); - Paragraph p = note.addParagraph(); - p.setText(pText); - note.run(p.getId()); - - ArgumentCaptor pCaptor = ArgumentCaptor.forClass(Paragraph.class); - verify(scheduler, only()).submit(pCaptor.capture()); - verify(interpreterFactory, times(2)).getInterpreter(anyString(), anyString()); - - assertEquals("Change paragraph text", "%jdbc(mysql) show databases", pCaptor.getValue().getEffectiveText()); - assertEquals("Change paragraph text", pText, pCaptor.getValue().getText()); - } - @Test public void putDefaultReplNameIfInterpreterSettingAbsent() { when(interpreterFactory.getDefaultInterpreterSetting(anyString())) @@ -138,6 +118,7 @@ public void addParagraphWithLastReplName() { Note note = new Note(repo, interpreterFactory, jobListenerFactory, index, credentials, noteEventListener); note.putDefaultReplName(); //set lastReplName + when(interpreterFactory.getInterpreter(note.getId(), "spark")).thenReturn(new MockInterpreter2(null)); Paragraph p = note.addParagraph(); @@ -153,6 +134,7 @@ public void insertParagraphWithLastReplName() { Note note = new Note(repo, interpreterFactory, jobListenerFactory, index, credentials, noteEventListener); note.putDefaultReplName(); //set lastReplName + when(interpreterFactory.getInterpreter(note.getId(), "spark")).thenReturn(new MockInterpreter2(null)); Paragraph p = note.insertParagraph(note.getParagraphs().size()); @@ -171,4 +153,20 @@ public void setLastReplName() { assertEquals("spark", note.getLastReplName()); } + + @Test + public void isBindingTest() { + Note note = spy(new Note()); + when(note.getId()).thenReturn("test1"); + InterpreterFactory mockInterpreterFactory = mock(InterpreterFactory.class); + note.setInterpreterFactory(mockInterpreterFactory); + + //when is not binding + assertFalse(note.isBinding("spark")); + + //when is binding + when(mockInterpreterFactory.getInterpreter("test1", "spark")). + thenReturn(new MockInterpreter2(null)); + assertTrue(note.isBinding("spark")); + } } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java index bfa97e05de3..648062eda2e 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java @@ -171,7 +171,7 @@ public void testReloadAllNotes() throws IOException { notebook.reloadAllNotes(null); notes = notebook.getAllNotes(); assertEquals(notes.size(), 2); - assertEquals(notes.get(1).id(), copiedNote.id()); + assertEquals(notes.get(1).getId(), copiedNote.getId()); assertEquals(notes.get(1).getName(), copiedNote.getName()); assertEquals(notes.get(1).getParagraphs(), copiedNote.getParagraphs()); @@ -283,13 +283,13 @@ public void testSchedule() throws InterruptedException, IOException{ config.put("enabled", true); config.put("cron", "* * * * * ?"); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); Thread.sleep(1*1000); // remove cron scheduler. config.put("cron", null); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); Thread.sleep(1000); dateFinished = p.getDateFinished(); assertNotNull(dateFinished); @@ -318,7 +318,7 @@ public void testAutoRestartInterpreterAfterSchedule() throws InterruptedExceptio config.put("cron", "1/3 * * * * ?"); config.put("releaseresource", true); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); MockInterpreter1 mock1 = ((MockInterpreter1) (((ClassloaderInterpreter) @@ -342,7 +342,7 @@ public void testAutoRestartInterpreterAfterSchedule() throws InterruptedExceptio // remove cron scheduler. config.put("cron", null); note.setConfig(config); - notebook.refreshCron(note.id()); + notebook.refreshCron(note.getId()); // make sure all paragraph has been executed assertNotNull(p.getDateFinished()); @@ -398,6 +398,16 @@ public void testCloneNote() throws IOException, CloneNotSupportedException, assertEquals(cp.getResult().message(), p.getResult().message()); } + @Test + public void testCloneNoteWithNoName() throws IOException, CloneNotSupportedException, + InterruptedException { + Note note = notebook.createNote(null); + factory.setInterpreters(note.getId(), factory.getDefaultInterpreterSettingList()); + + Note cloneNote = notebook.cloneNote(note.getId(), null, null); + assertEquals(cloneNote.getName(), "Note " + cloneNote.getId()); + } + @Test public void testCloneNoteWithExceptionResult() throws IOException, CloneNotSupportedException, InterruptedException { @@ -445,7 +455,7 @@ public void testResourceRemovealOnParagraphNoteRemove() throws IOException { assertEquals(1, ResourcePoolUtils.getAllResources().size()); // remove note - notebook.removeNote(note.id(), null); + notebook.removeNote(note.getId(), null); assertEquals(0, ResourcePoolUtils.getAllResources().size()); } @@ -463,20 +473,20 @@ public void testAngularObjectRemovalOnNotebookRemove() throws InterruptedExcepti Paragraph p1 = note.addParagraph(); // add paragraph scope object - registry.add("o1", "object1", note.id(), p1.getId()); + registry.add("o1", "object1", note.getId(), p1.getId()); // add notebook scope object - registry.add("o2", "object2", note.id(), null); + registry.add("o2", "object2", note.getId(), null); // add global scope object registry.add("o3", "object3", null, null); // remove notebook - notebook.removeNote(note.id(), null); + notebook.removeNote(note.getId(), null); // notebook scope or paragraph scope object should be removed - assertNull(registry.get("o1", note.id(), null)); - assertNull(registry.get("o2", note.id(), p1.getId())); + assertNull(registry.get("o1", note.getId(), null)); + assertNull(registry.get("o2", note.getId(), p1.getId())); // global object sould be remained assertNotNull(registry.get("o3", null, null)); @@ -496,10 +506,10 @@ public void testAngularObjectRemovalOnParagraphRemove() throws InterruptedExcept Paragraph p1 = note.addParagraph(); // add paragraph scope object - registry.add("o1", "object1", note.id(), p1.getId()); + registry.add("o1", "object1", note.getId(), p1.getId()); // add notebook scope object - registry.add("o2", "object2", note.id(), null); + registry.add("o2", "object2", note.getId(), null); // add global scope object registry.add("o3", "object3", null, null); @@ -508,10 +518,10 @@ public void testAngularObjectRemovalOnParagraphRemove() throws InterruptedExcept note.removeParagraph(p1.getId()); // paragraph scope should be removed - assertNull(registry.get("o1", note.id(), null)); + assertNull(registry.get("o1", note.getId(), null)); // notebook scope and global object sould be remained - assertNotNull(registry.get("o2", note.id(), null)); + assertNotNull(registry.get("o2", note.getId(), null)); assertNotNull(registry.get("o3", null, null)); } @@ -527,7 +537,7 @@ public void testAngularObjectRemovalOnInterpreterRestart() throws InterruptedExc .getAngularObjectRegistry(); // add local scope object - registry.add("o1", "object1", note.id(), null); + registry.add("o1", "object1", note.getId(), null); // add global scope object registry.add("o2", "object2", null, null); @@ -537,9 +547,9 @@ public void testAngularObjectRemovalOnInterpreterRestart() throws InterruptedExc .getAngularObjectRegistry(); // local and global scope object should be removed - assertNull(registry.get("o1", note.id(), null)); + assertNull(registry.get("o1", note.getId(), null)); assertNull(registry.get("o2", null, null)); - notebook.removeNote(note.id(), null); + notebook.removeNote(note.getId(), null); } @Test @@ -548,43 +558,43 @@ public void testPermissions() throws IOException { Note note = notebook.createNote(null); NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); // empty owners, readers and writers means note is public - assertEquals(notebookAuthorization.isOwner(note.id(), + assertEquals(notebookAuthorization.isOwner(note.getId(), new HashSet(Arrays.asList("user2"))), true); - assertEquals(notebookAuthorization.isReader(note.id(), + assertEquals(notebookAuthorization.isReader(note.getId(), new HashSet(Arrays.asList("user2"))), true); - assertEquals(notebookAuthorization.isWriter(note.id(), + assertEquals(notebookAuthorization.isWriter(note.getId(), new HashSet(Arrays.asList("user2"))), true); - notebookAuthorization.setOwners(note.id(), + notebookAuthorization.setOwners(note.getId(), new HashSet(Arrays.asList("user1"))); - notebookAuthorization.setReaders(note.id(), + notebookAuthorization.setReaders(note.getId(), new HashSet(Arrays.asList("user1", "user2"))); - notebookAuthorization.setWriters(note.id(), + notebookAuthorization.setWriters(note.getId(), new HashSet(Arrays.asList("user1"))); - assertEquals(notebookAuthorization.isOwner(note.id(), + assertEquals(notebookAuthorization.isOwner(note.getId(), new HashSet(Arrays.asList("user2"))), false); - assertEquals(notebookAuthorization.isOwner(note.id(), + assertEquals(notebookAuthorization.isOwner(note.getId(), new HashSet(Arrays.asList("user1"))), true); - assertEquals(notebookAuthorization.isReader(note.id(), + assertEquals(notebookAuthorization.isReader(note.getId(), new HashSet(Arrays.asList("user3"))), false); - assertEquals(notebookAuthorization.isReader(note.id(), + assertEquals(notebookAuthorization.isReader(note.getId(), new HashSet(Arrays.asList("user2"))), true); - assertEquals(notebookAuthorization.isWriter(note.id(), + assertEquals(notebookAuthorization.isWriter(note.getId(), new HashSet(Arrays.asList("user2"))), false); - assertEquals(notebookAuthorization.isWriter(note.id(), + assertEquals(notebookAuthorization.isWriter(note.getId(), new HashSet(Arrays.asList("user1"))), true); // Test clearing of permssions - notebookAuthorization.setReaders(note.id(), Sets.newHashSet()); - assertEquals(notebookAuthorization.isReader(note.id(), + notebookAuthorization.setReaders(note.getId(), Sets.newHashSet()); + assertEquals(notebookAuthorization.isReader(note.getId(), new HashSet(Arrays.asList("user2"))), true); - assertEquals(notebookAuthorization.isReader(note.id(), + assertEquals(notebookAuthorization.isReader(note.getId(), new HashSet(Arrays.asList("user3"))), true); - notebook.removeNote(note.id(), null); + notebook.removeNote(note.getId(), null); } @Test @@ -777,8 +787,8 @@ public void onParagraphStatusChange(Paragraph p, Status status) { note1.removeParagraph(p1.getId()); assertEquals(1, onParagraphRemove.get()); - List settings = notebook.getBindedInterpreterSettingsIds(note1.id()); - notebook.bindInterpretersToNote(note1.id(), new LinkedList()); + List settings = notebook.getBindedInterpreterSettingsIds(note1.getId()); + notebook.bindInterpretersToNote(note1.getId(), new LinkedList()); assertEquals(settings.size(), unbindInterpreter.get()); notebook.removeNote(note1.getId(), null); diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java index 1f8519cb399..668914ae38a 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java @@ -73,32 +73,6 @@ public void replNameEndsWithWhitespace() { assertEquals("md", Paragraph.getRequiredReplName(text)); } - @Test - public void effectiveTextTest() { - InterpreterFactory interpreterFactory = mock(InterpreterFactory.class); - Interpreter interpreter = mock(Interpreter.class); - Note note = mock(Note.class); - - Paragraph p = new Paragraph("paragraph", note, null, interpreterFactory); - p.setText("%h2 show databases"); - p.setEffectiveText("%jdbc(h2) show databases"); - assertEquals("Get right replName", "jdbc", p.getRequiredReplName()); - assertEquals("Get right scriptBody", "(h2) show databases", p.getScriptBody()); - - when(interpreterFactory.getInterpreter(anyString(), eq("jdbc"))).thenReturn(interpreter); - when(interpreter.getFormType()).thenReturn(Interpreter.FormType.NATIVE); - when(note.getId()).thenReturn("noteId"); - - try { - p.jobRun(); - } catch (Throwable throwable) { - // Do nothing - } - - assertEquals("Erase effective Text", "h2", p.getRequiredReplName()); - assertEquals("Erase effective Text", "show databases", p.getScriptBody()); - } - @Test public void should_extract_variable_from_angular_object_registry() throws Exception { //Given diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java index b1d4b384e01..2232ea4313d 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java @@ -234,7 +234,7 @@ public void getRevisionTest() throws IOException { assertThat(paragraphCount_2).isEqualTo(paragraphCount_1 + 1); // get note from revision 1 - Note noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1, null); + Note noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1.id, null); assertThat(noteRevision_1.getParagraphs().size()).isEqualTo(paragraphCount_1); // get current note @@ -252,7 +252,7 @@ public void getRevisionTest() throws IOException { assertThat(paragraphCount_3).isEqualTo(paragraphCount_2 + 1); // get revision 1 again - noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1, null); + noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1.id, null); assertThat(noteRevision_1.getParagraphs().size()).isEqualTo(paragraphCount_1); // check that note is unchanged @@ -287,7 +287,7 @@ public void getRevisionFailTest() throws IOException { int paragraphCount_2 = note.getParagraphs().size(); // get note from revision 1 - Note noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1, null); + Note noteRevision_1 = notebookRepo.get(TEST_NOTE_ID, revision_1.id, null); assertThat(noteRevision_1.getParagraphs().size()).isEqualTo(paragraphCount_1); // get current note @@ -296,7 +296,7 @@ public void getRevisionFailTest() throws IOException { // test for absent revision Revision absentRevision = new Revision("absentId", StringUtils.EMPTY, 0); - note = notebookRepo.get(TEST_NOTE_ID, absentRevision, null); + note = notebookRepo.get(TEST_NOTE_ID, absentRevision.id, null); assertThat(note).isNull(); } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java index 0c67d79789b..bd13120a4a3 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java @@ -84,6 +84,7 @@ public void setUp() throws Exception { System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(), mainNotebookDir.getAbsolutePath()); System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1,org.apache.zeppelin.interpreter.mock.MockInterpreter2"); System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE.getVarName(), "org.apache.zeppelin.notebook.repo.VFSNotebookRepo,org.apache.zeppelin.notebook.repo.mock.VFSNotebookRepoMock"); + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC.getVarName(), "false"); LOG.info("main Note dir : " + mainNotePath); LOG.info("secondary note dir : " + secNotePath); conf = ZeppelinConfiguration.create(); @@ -220,6 +221,54 @@ public void testSyncOnReloadedList() throws IOException { assertEquals(1, notebookRepoSync.list(1, null).size()); } + @Test + public void testOneWaySyncOnReloadedList() throws IOException, SchedulerException { + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(), mainNotebookDir.getAbsolutePath()); + System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC.getVarName(), "true"); + conf = ZeppelinConfiguration.create(); + notebookRepoSync = new NotebookRepoSync(conf); + notebookSync = new Notebook(conf, notebookRepoSync, schedulerFactory, factory, this, search, + notebookAuthorization, credentials); + + // check that both storage repos are empty + assertTrue(notebookRepoSync.getRepoCount() > 1); + assertEquals(0, notebookRepoSync.list(0, null).size()); + assertEquals(0, notebookRepoSync.list(1, null).size()); + + File srcDir = new File("src/test/resources/2A94M5J1Z"); + File destDir = new File(secNotebookDir + "/2A94M5J1Z"); + + // copy manually new notebook into secondary storage repo and check repos + try { + FileUtils.copyDirectory(srcDir, destDir); + } catch (IOException e) { + LOG.error(e.toString(), e); + } + assertEquals(0, notebookRepoSync.list(0, null).size()); + assertEquals(1, notebookRepoSync.list(1, null).size()); + + // after reloading the notebook should be wiped from secondary storage + notebookSync.reloadAllNotes(null); + assertEquals(0, notebookRepoSync.list(0, null).size()); + assertEquals(0, notebookRepoSync.list(1, null).size()); + + destDir = new File(mainNotebookDir + "/2A94M5J1Z"); + + // copy manually new notebook into primary storage repo and check repos + try { + FileUtils.copyDirectory(srcDir, destDir); + } catch (IOException e) { + LOG.error(e.toString(), e); + } + assertEquals(1, notebookRepoSync.list(0, null).size()); + assertEquals(0, notebookRepoSync.list(1, null).size()); + + // after reloading notebooks repos should be synchronized + notebookSync.reloadAllNotes(null); + assertEquals(1, notebookRepoSync.list(0, null).size()); + assertEquals(1, notebookRepoSync.list(1, null).size()); + } + @Test public void testCheckpointOneStorage() throws IOException, SchedulerException { System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE.getVarName(), "org.apache.zeppelin.notebook.repo.GitNotebookRepo"); diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepoTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepoTest.java index 938521a3ab8..720dd702faa 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepoTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepoTest.java @@ -132,7 +132,7 @@ public void testGetAllNotes() throws IOException { public void testGetNote() throws IOException { Note notebook = repo.get("AAAAA", null); assertThat(notebook).isNotNull(); - assertThat(notebook.id()).isEqualTo("2A94M5J1Z"); + assertThat(notebook.getId()).isEqualTo("2A94M5J1Z"); } @Test