mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge branch 'master' into extends-zrun-remote-transaction
This commit is contained in:
commit
8a54917987
34 changed files with 650 additions and 211 deletions
|
|
@ -23,7 +23,9 @@ cache:
|
|||
- .spark-dist
|
||||
- ${HOME}/.m2
|
||||
- ${HOME}/R
|
||||
- .node_modules
|
||||
- zeppelin-web/node
|
||||
- zeppelin-web/node_modules
|
||||
- zeppelin-web/bower_components
|
||||
|
||||
addons:
|
||||
apt:
|
||||
|
|
@ -65,6 +67,7 @@ matrix:
|
|||
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:
|
||||
- echo "MAVEN_OPTS='-Xms1024M -Xmx2048M -XX:MaxPermSize=1024m -XX:-UseGCOverheadLimit'" >> ~/.mavenrc
|
||||
- "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
|
||||
|
|
@ -75,7 +78,7 @@ before_install:
|
|||
- ./dev/change_scala_version.sh $SCALA_VER
|
||||
|
||||
install:
|
||||
- mvn $BUILD_FLAG $PROFILE -B
|
||||
- mvn -Dorg.slf4j.simpleLogger.defaultLogLevel=warn $BUILD_FLAG $PROFILE -B
|
||||
|
||||
before_script:
|
||||
- travis_retry ./testing/downloadSpark.sh $SPARK_VER $HADOOP_VER
|
||||
|
|
@ -84,7 +87,6 @@ before_script:
|
|||
|
||||
script:
|
||||
- mvn -Dorg.slf4j.simpleLogger.defaultLogLevel=warn $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}"
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@
|
|||
|
||||
<property>
|
||||
<name>zeppelin.interpreters</name>
|
||||
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
|
||||
<value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.python.PythonInterpreterPandasSql,org.apache.zeppelin.python.PythonCondaInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
|
||||
<description>Comma separated interpreter configurations. First interpreter become a default</description>
|
||||
</property>
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ Congratulations, you have successfully installed Apache Zeppelin! Here are few s
|
|||
* Check [JDBC Interpreter](../interpreter/jdbc.html) to know more about configure and uses multiple JDBC data sources.
|
||||
|
||||
#### Zeppelin with Python ...
|
||||
* Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib, Pandas integration.
|
||||
* Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib, Pandas, Conda integration.
|
||||
|
||||
|
||||
#### Multi-user environment ...
|
||||
|
|
|
|||
|
|
@ -59,6 +59,31 @@ To access the help, type **help()**
|
|||
## Python modules
|
||||
The interpreter can use all modules already installed (with pip, easy_install...)
|
||||
|
||||
## Conda
|
||||
[Conda](http://conda.pydata.org/) is an package management system and environment management system for python.
|
||||
`%python.conda` interpreter lets you change between environments.
|
||||
|
||||
#### Usage
|
||||
|
||||
List your environments
|
||||
|
||||
```
|
||||
%python.conda
|
||||
```
|
||||
|
||||
Activate an environment
|
||||
|
||||
```
|
||||
%python.conda activate [ENVIRONMENT_NAME]
|
||||
```
|
||||
|
||||
Deactivate
|
||||
|
||||
```
|
||||
%python.conda deactivate
|
||||
```
|
||||
|
||||
|
||||
## Using Zeppelin Dynamic Forms
|
||||
You can leverage [Zeppelin Dynamic Form]({{BASE_PATH}}/manual/dynamicform.html) inside your Python code.
|
||||
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@
|
|||
"progressUpdateIntervalMs": 500
|
||||
}
|
||||
],
|
||||
"name": "Zeppelin Tutorial",
|
||||
"name": "Zeppelin Tutorial/Basic Features (Spark)",
|
||||
"id": "2A94M5J1Z",
|
||||
"angularObjects": {
|
||||
"2B6FF8NNU": [],
|
||||
|
|
@ -344,4 +344,4 @@
|
|||
"looknfeel": "default"
|
||||
},
|
||||
"info": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1037,7 +1037,7 @@
|
|||
"progressUpdateIntervalMs": 500
|
||||
}
|
||||
],
|
||||
"name": "R Tutorial",
|
||||
"name": "Zeppelin Tutorial/R (SparkR)",
|
||||
"id": "2BWJFTXKJ",
|
||||
"lastReplName": {
|
||||
"value": ""
|
||||
|
|
@ -1066,4 +1066,4 @@
|
|||
"looknfeel": "default"
|
||||
},
|
||||
"info": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -743,7 +743,7 @@
|
|||
"progressUpdateIntervalMs": 500
|
||||
}
|
||||
],
|
||||
"name": "Mahout Tutorial",
|
||||
"name": "Zeppelin Tutorial/Using Mahout",
|
||||
"id": "2BYEZ5EVK",
|
||||
"angularObjects": {
|
||||
"2BWWSVNY7:shared_process": [],
|
||||
|
|
@ -769,4 +769,4 @@
|
|||
},
|
||||
"config": {},
|
||||
"info": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@
|
|||
"progressUpdateIntervalMs": 500
|
||||
}
|
||||
],
|
||||
"name": "Zeppelin Tutorial: Python - matplotlib basic",
|
||||
"name": "Zeppelin Tutorial/Matplotlib (Python • PySpark)",
|
||||
"id": "2C2AUG798",
|
||||
"angularObjects": {
|
||||
"2BWWUCUVW:shared_process": [],
|
||||
|
|
@ -693,4 +693,4 @@
|
|||
},
|
||||
"config": {},
|
||||
"info": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
pom.xml
2
pom.xml
|
|
@ -437,7 +437,7 @@
|
|||
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>3.0.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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.python;
|
||||
|
||||
import org.apache.zeppelin.interpreter.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Conda support
|
||||
*/
|
||||
public class PythonCondaInterpreter extends Interpreter {
|
||||
Logger logger = LoggerFactory.getLogger(PythonCondaInterpreter.class);
|
||||
|
||||
Pattern condaEnvListPattern = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
|
||||
Pattern listPattern = Pattern.compile("env\\s*list\\s?");
|
||||
Pattern activatePattern = Pattern.compile("activate\\s*(.*)");
|
||||
Pattern deactivatePattern = Pattern.compile("deactivate");
|
||||
Pattern helpPattern = Pattern.compile("help");
|
||||
String pythonCommand = null;
|
||||
|
||||
public PythonCondaInterpreter(Properties property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterpreterResult interpret(String st, InterpreterContext context) {
|
||||
InterpreterOutput out = context.out;
|
||||
|
||||
Matcher listMatcher = listPattern.matcher(st);
|
||||
Matcher activateMatcher = activatePattern.matcher(st);
|
||||
Matcher deactivateMatcher = deactivatePattern.matcher(st);
|
||||
Matcher helpMatcher = helpPattern.matcher(st);
|
||||
|
||||
if (st == null || st.isEmpty() || listMatcher.matches()) {
|
||||
listEnv(out);
|
||||
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
|
||||
} else if (activateMatcher.matches()) {
|
||||
String envName = activateMatcher.group(1);
|
||||
pythonCommand = "conda run -n " + envName + " \"python -iu\"";
|
||||
restartPythonProcess();
|
||||
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "\"" + envName + "\" activated");
|
||||
} else if (deactivateMatcher.matches()) {
|
||||
pythonCommand = null;
|
||||
restartPythonProcess();
|
||||
return new InterpreterResult(InterpreterResult.Code.SUCCESS, "Deactivated");
|
||||
} else if (helpMatcher.matches()) {
|
||||
printUsage(out);
|
||||
return new InterpreterResult(InterpreterResult.Code.SUCCESS);
|
||||
} else {
|
||||
return new InterpreterResult(InterpreterResult.Code.ERROR, "Not supported command: " + st);
|
||||
}
|
||||
}
|
||||
|
||||
private void restartPythonProcess() {
|
||||
PythonInterpreter python = getPythonInterpreter();
|
||||
python.close();
|
||||
python.open();
|
||||
}
|
||||
|
||||
protected PythonInterpreter getPythonInterpreter() {
|
||||
LazyOpenInterpreter lazy = null;
|
||||
PythonInterpreter python = null;
|
||||
Interpreter p = getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName());
|
||||
|
||||
while (p instanceof WrappedInterpreter) {
|
||||
if (p instanceof LazyOpenInterpreter) {
|
||||
lazy = (LazyOpenInterpreter) p;
|
||||
}
|
||||
p = ((WrappedInterpreter) p).getInnerInterpreter();
|
||||
}
|
||||
python = (PythonInterpreter) p;
|
||||
|
||||
if (lazy != null) {
|
||||
lazy.open();
|
||||
}
|
||||
return python;
|
||||
}
|
||||
|
||||
public String getPythonCommand() {
|
||||
return pythonCommand;
|
||||
}
|
||||
|
||||
private void listEnv(InterpreterOutput out) {
|
||||
StringBuilder sb = createStringBuilder();
|
||||
try {
|
||||
int exit = runCommand(sb, "conda", "env", "list");
|
||||
if (exit == 0) {
|
||||
out.setType(InterpreterResult.Type.HTML);
|
||||
out.write("<h4>Conda environments</h4>\n");
|
||||
// start table
|
||||
out.write("<div style=\"display:table\">\n");
|
||||
String[] lines = sb.toString().split("\n");
|
||||
for (String s : lines) {
|
||||
if (s == null || s.isEmpty() || s.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
Matcher match = condaEnvListPattern.matcher(s);
|
||||
|
||||
if (!match.matches()) {
|
||||
continue;
|
||||
}
|
||||
out.write(String.format("<div style=\"display:table-row\">" +
|
||||
"<div style=\"display:table-cell;width:150px\">%s</div>" +
|
||||
"<div style=\"display:table-cell;\">%s</div>" +
|
||||
"</div>\n",
|
||||
match.group(1), match.group(2)));
|
||||
}
|
||||
// end table
|
||||
out.write("</div><br />\n");
|
||||
out.write("<small><code>%python.conda help</code> for the usage</small>\n");
|
||||
} else {
|
||||
out.write("Failed to run 'conda' " + exit + "\n");
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new InterpreterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void printUsage(InterpreterOutput out) {
|
||||
try {
|
||||
out.setType(InterpreterResult.Type.HTML);
|
||||
out.writeResource("output_templates/usage.html");
|
||||
} catch (IOException e) {
|
||||
logger.error("Can't print usage", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(InterpreterContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormType getFormType() {
|
||||
return FormType.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgress(InterpreterContext context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int runCommand(StringBuilder sb, String ... command)
|
||||
throws IOException, InterruptedException {
|
||||
ProcessBuilder builder = new ProcessBuilder(command);
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = builder.start();
|
||||
InputStream stdout = process.getInputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line);
|
||||
sb.append("\n");
|
||||
}
|
||||
int r = process.waitFor(); // Let the process finish.
|
||||
return r;
|
||||
}
|
||||
|
||||
protected StringBuilder createStringBuilder() {
|
||||
return new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,7 @@ public class PythonInterpreter extends Interpreter {
|
|||
try {
|
||||
if (process != null) {
|
||||
process.close();
|
||||
process = null;
|
||||
}
|
||||
if (gatewayServer != null) {
|
||||
gatewayServer.shutdown();
|
||||
|
|
@ -198,7 +199,12 @@ public class PythonInterpreter extends Interpreter {
|
|||
|
||||
public PythonProcess getPythonProcess() {
|
||||
if (process == null) {
|
||||
return new PythonProcess(getProperty(ZEPPELIN_PYTHON));
|
||||
PythonCondaInterpreter conda = getCondaInterpreter();
|
||||
String binPath = getProperty(ZEPPELIN_PYTHON);
|
||||
if (conda != null && conda.getPythonCommand() != null) {
|
||||
binPath = conda.getPythonCommand();
|
||||
}
|
||||
return new PythonProcess(binPath);
|
||||
} else {
|
||||
return process;
|
||||
}
|
||||
|
|
@ -278,5 +284,25 @@ public class PythonInterpreter extends Interpreter {
|
|||
public int getMaxResult() {
|
||||
return maxResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private PythonCondaInterpreter getCondaInterpreter() {
|
||||
LazyOpenInterpreter lazy = null;
|
||||
PythonCondaInterpreter conda = null;
|
||||
Interpreter p = getInterpreterInTheSameSessionByClassName(
|
||||
PythonCondaInterpreter.class.getName());
|
||||
|
||||
while (p instanceof WrappedInterpreter) {
|
||||
if (p instanceof LazyOpenInterpreter) {
|
||||
lazy = (LazyOpenInterpreter) p;
|
||||
}
|
||||
p = ((WrappedInterpreter) p).getInnerInterpreter();
|
||||
}
|
||||
conda = (PythonCondaInterpreter) p;
|
||||
|
||||
if (lazy != null) {
|
||||
lazy.open();
|
||||
}
|
||||
return conda;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,23 @@ public class PythonProcess {
|
|||
}
|
||||
|
||||
public void open() throws IOException {
|
||||
ProcessBuilder builder = new ProcessBuilder(binPath, "-iu");
|
||||
ProcessBuilder builder;
|
||||
boolean hasParams = binPath.split(" ").length > 1;
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
||||
if (hasParams) {
|
||||
builder = new ProcessBuilder(binPath.split(" "));
|
||||
} else {
|
||||
builder = new ProcessBuilder(binPath, "-iu");
|
||||
}
|
||||
} else {
|
||||
String cmd;
|
||||
if (hasParams) {
|
||||
cmd = binPath;
|
||||
} else {
|
||||
cmd = binPath + " -iu";
|
||||
}
|
||||
builder = new ProcessBuilder("bash", "-c", cmd);
|
||||
}
|
||||
|
||||
builder.redirectErrorStream(true);
|
||||
process = builder.start();
|
||||
|
|
|
|||
|
|
@ -32,5 +32,16 @@
|
|||
"language": "sql",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "python",
|
||||
"name": "conda",
|
||||
"className": "org.apache.zeppelin.python.PythonCondaInterpreter",
|
||||
"properties": {
|
||||
},
|
||||
"editor":{
|
||||
"language": "sh",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
27
python/src/main/resources/output_templates/usage.html
Normal file
27
python/src/main/resources/output_templates/usage.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<h4>Usage</h4>
|
||||
<div>
|
||||
Activate an environment (python interpreter will be restarted)
|
||||
<pre>%python.conda activate [ENV NAME]</pre>
|
||||
</div>
|
||||
<div>
|
||||
Deactivate
|
||||
<pre>%python.conda deactivate</pre>
|
||||
</div>
|
||||
<div>
|
||||
List the Conda environments
|
||||
<pre>%python.conda</pre>
|
||||
</div>
|
||||
|
||||
|
|
@ -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.python;
|
||||
|
||||
import org.apache.zeppelin.display.GUI;
|
||||
import org.apache.zeppelin.interpreter.*;
|
||||
import org.apache.zeppelin.user.AuthenticationInfo;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class PythonCondaInterpreterTest implements InterpreterOutputListener {
|
||||
private PythonCondaInterpreter conda;
|
||||
private PythonInterpreter python;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
conda = spy(new PythonCondaInterpreter(new Properties()));
|
||||
python = mock(PythonInterpreter.class);
|
||||
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", Arrays.asList(python, conda));
|
||||
python.setInterpreterGroup(group);
|
||||
conda.setInterpreterGroup(group);
|
||||
|
||||
doReturn(python).when(conda).getPythonInterpreter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListEnv() throws IOException, InterruptedException {
|
||||
InterpreterContext context = getInterpreterContext();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("#comment\n\nenv1 * /path1\nenv2\t/path2\n");
|
||||
|
||||
doReturn(sb).when(conda).createStringBuilder();
|
||||
doReturn(0).when(conda)
|
||||
.runCommand(any(StringBuilder.class), anyString(), anyString(), anyString());
|
||||
|
||||
// list available env
|
||||
InterpreterResult result = conda.interpret("", context);
|
||||
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
|
||||
|
||||
String out = new String(context.out.toByteArray());
|
||||
assertTrue(out.contains(">env1<"));
|
||||
assertTrue(out.contains(">/path1<"));
|
||||
assertTrue(out.contains(">env2<"));
|
||||
assertTrue(out.contains(">/path2<"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateEnv() {
|
||||
InterpreterContext context = getInterpreterContext();
|
||||
conda.interpret("activate env", context);
|
||||
verify(python, times(1)).open();
|
||||
verify(python, times(1)).close();
|
||||
assertEquals("conda run -n env \"python -iu\"", conda.getPythonCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivate() {
|
||||
InterpreterContext context = getInterpreterContext();
|
||||
conda.interpret("deactivate", context);
|
||||
verify(python, times(1)).open();
|
||||
verify(python, times(1)).close();
|
||||
assertEquals(null, conda.getPythonCommand());
|
||||
}
|
||||
|
||||
private InterpreterContext getInterpreterContext() {
|
||||
return new InterpreterContext(
|
||||
"noteId",
|
||||
"paragraphId",
|
||||
"paragraphTitle",
|
||||
"paragraphText",
|
||||
new AuthenticationInfo(),
|
||||
new HashMap<String, Object>(),
|
||||
new GUI(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new InterpreterOutput(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppend(InterpreterOutput out, byte[] line) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(InterpreterOutput out, byte[] output) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -37,9 +37,13 @@ import java.io.IOException;
|
|||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.zeppelin.interpreter.ClassloaderInterpreter;
|
||||
import org.apache.zeppelin.interpreter.Interpreter;
|
||||
import org.apache.zeppelin.interpreter.InterpreterGroup;
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -82,8 +86,15 @@ public class PythonInterpreterTest {
|
|||
}
|
||||
});
|
||||
|
||||
// python interpreter
|
||||
pythonInterpreter = spy(new PythonInterpreter(getPythonTestProperties()));
|
||||
|
||||
// create interpreter group
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", new LinkedList<Interpreter>());
|
||||
group.get("note").add(pythonInterpreter);
|
||||
pythonInterpreter.setInterpreterGroup(group);
|
||||
|
||||
when(pythonInterpreter.getPythonProcess()).thenReturn(mockPythonProcess);
|
||||
when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn("ImportError");
|
||||
}
|
||||
|
|
@ -220,15 +231,24 @@ public class PythonInterpreterTest {
|
|||
|
||||
@Test
|
||||
public void checkMultiRowErrorFails() {
|
||||
|
||||
PythonInterpreter pythonInterpreter = new PythonInterpreter(
|
||||
PythonInterpreterTest.getPythonTestProperties()
|
||||
);
|
||||
// create interpreter group
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", new LinkedList<Interpreter>());
|
||||
group.get("note").add(pythonInterpreter);
|
||||
pythonInterpreter.setInterpreterGroup(group);
|
||||
|
||||
pythonInterpreter.open();
|
||||
|
||||
String codeRaiseException = "raise Exception(\"test exception\")";
|
||||
InterpreterResult ret = pythonInterpreter.interpret(codeRaiseException, null);
|
||||
|
||||
assertNotNull("Interpreter result for raise exception is Null", ret);
|
||||
|
||||
System.err.println("ret = '" + ret + "'");
|
||||
assertEquals(InterpreterResult.Code.ERROR, ret.code());
|
||||
assertTrue(ret.message().length() > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,13 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.apache.zeppelin.interpreter.Interpreter;
|
||||
import org.apache.zeppelin.interpreter.InterpreterGroup;
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Python interpreter unit test that user real Python
|
||||
*
|
||||
|
|
@ -46,6 +50,11 @@ public class PythonInterpreterWithPythonInstalledTest {
|
|||
//given
|
||||
PythonInterpreter realPython = new PythonInterpreter(
|
||||
PythonInterpreterTest.getPythonTestProperties());
|
||||
// create interpreter group
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", Arrays.asList((Interpreter) realPython));
|
||||
realPython.setInterpreterGroup(group);
|
||||
|
||||
realPython.open();
|
||||
|
||||
//when
|
||||
|
|
@ -65,6 +74,9 @@ public class PythonInterpreterWithPythonInstalledTest {
|
|||
//given
|
||||
PythonInterpreter realPython = new PythonInterpreter(
|
||||
PythonInterpreterTest.getPythonTestProperties());
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", Arrays.asList((Interpreter) realPython));
|
||||
realPython.setInterpreterGroup(group);
|
||||
realPython.open();
|
||||
|
||||
//when
|
||||
|
|
@ -84,6 +96,9 @@ public class PythonInterpreterWithPythonInstalledTest {
|
|||
//given
|
||||
PythonInterpreter realPython = new PythonInterpreter(
|
||||
PythonInterpreterTest.getPythonTestProperties());
|
||||
InterpreterGroup group = new InterpreterGroup();
|
||||
group.put("note", Arrays.asList((Interpreter) realPython));
|
||||
realPython.setInterpreterGroup(group);
|
||||
realPython.open();
|
||||
|
||||
//when
|
||||
|
|
|
|||
|
|
@ -504,5 +504,4 @@
|
|||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -571,6 +571,20 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
broadcast(noteId, new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList));
|
||||
}
|
||||
|
||||
public void broadcastParagraph(Note note, Paragraph p) {
|
||||
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
|
||||
}
|
||||
|
||||
private void broadcastNewParagraph(Note note, Paragraph para) {
|
||||
LOG.info("Broadcasting paragraph on run call instead of note.");
|
||||
int paraIndex = note.getParagraphs().indexOf(para);
|
||||
broadcast(
|
||||
note.getId(),
|
||||
new Message(OP.PARAGRAPH_ADDED)
|
||||
.put("paragraph", para)
|
||||
.put("index", paraIndex));
|
||||
}
|
||||
|
||||
public void broadcastNoteList(AuthenticationInfo subject, HashSet userAndRoles) {
|
||||
if (subject == null) {
|
||||
subject = new AuthenticationInfo(StringUtils.EMPTY);
|
||||
|
|
@ -718,7 +732,10 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
|
||||
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
|
||||
note.persist(subject);
|
||||
broadcastNote(note);
|
||||
broadcast(note.getId(), new Message(OP.NOTE_UPDATED)
|
||||
.put("name", name)
|
||||
.put("config", config)
|
||||
.put("info", note.getInfo()));
|
||||
broadcastNoteList(subject, userAndRoles);
|
||||
}
|
||||
}
|
||||
|
|
@ -860,7 +877,7 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
p.setTitle((String) fromMessage.get("title"));
|
||||
p.setText((String) fromMessage.get("paragraph"));
|
||||
note.persist(subject);
|
||||
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
|
||||
broadcastParagraph(note, p);;
|
||||
}
|
||||
|
||||
private void cloneNote(NotebookSocket conn, HashSet<String> userAndRoles,
|
||||
|
|
@ -933,9 +950,12 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
|
||||
/** We dont want to remove the last paragraph */
|
||||
if (!note.isLastParagraph(paragraphId)) {
|
||||
note.removeParagraph(subject.getUser(), paragraphId);
|
||||
Paragraph para = note.removeParagraph(subject.getUser(), paragraphId);
|
||||
note.persist(subject);
|
||||
broadcastNote(note);
|
||||
if (para != null) {
|
||||
broadcast(note.getId(), new Message(OP.PARAGRAPH_REMOVED).
|
||||
put("id", para.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -953,9 +973,9 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
userAndRoles, notebookAuthorization.getWriters(noteId));
|
||||
return;
|
||||
}
|
||||
|
||||
note.clearParagraphOutput(paragraphId);
|
||||
broadcastNote(note);
|
||||
Paragraph paragraph = note.getParagraph(paragraphId);
|
||||
broadcastParagraph(note, paragraph);
|
||||
}
|
||||
|
||||
private void completion(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
|
||||
|
|
@ -1240,7 +1260,9 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
|
||||
note.moveParagraph(paragraphId, newIndex);
|
||||
note.persist(subject);
|
||||
broadcastNote(note);
|
||||
broadcast(note.getId(), new Message(OP.PARAGRAPH_MOVED)
|
||||
.put("id", paragraphId)
|
||||
.put("index", newIndex));
|
||||
}
|
||||
|
||||
private void insertParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
|
||||
|
|
@ -1257,9 +1279,9 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
return;
|
||||
}
|
||||
|
||||
note.insertParagraph(index);
|
||||
Paragraph newPara = note.insertParagraph(index);
|
||||
note.persist(subject);
|
||||
broadcastNote(note);
|
||||
broadcastNewParagraph(note, newPara);
|
||||
}
|
||||
|
||||
private void cancelParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
|
||||
|
|
@ -1316,7 +1338,8 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
boolean isTheLastParagraph = note.isLastParagraph(p.getId());
|
||||
if (!(text.trim().equals(p.getMagic()) || Strings.isNullOrEmpty(text)) &&
|
||||
isTheLastParagraph) {
|
||||
note.addParagraph();
|
||||
Paragraph newPara = note.addParagraph();
|
||||
broadcastNewParagraph(note, newPara);
|
||||
}
|
||||
|
||||
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
|
||||
|
|
@ -1700,8 +1723,9 @@ public class NotebookServer extends WebSocketServlet implements
|
|||
LOG.error(e.toString(), e);
|
||||
}
|
||||
}
|
||||
notebookServer.broadcastNote(note);
|
||||
|
||||
if (job instanceof Paragraph) {
|
||||
notebookServer.broadcastParagraph(note, (Paragraph) job);
|
||||
}
|
||||
try {
|
||||
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
|
||||
} catch (IOException e) {
|
||||
|
|
|
|||
|
|
@ -440,8 +440,9 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
|
|||
|
||||
waitForParagraph(1, "READY");
|
||||
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.SHIFT + "5");
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys("md" + Keys.ENTER);
|
||||
setTextOfParagraph(1, "%md");
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.ARROW_RIGHT);
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.ENTER);
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.SHIFT + "3");
|
||||
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(" abc");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,57 @@
|
|||
# Zeppelin Web Application
|
||||
|
||||
This is Zeppelin's frontend project.
|
||||
|
||||
## Development Guide
|
||||
|
||||
## Compile Zeppelin web
|
||||
### Packaging
|
||||
|
||||
### New environment
|
||||
If you want to package the zeppelin-web only, simply run this command in this folder.
|
||||
This will download all the dependencies including node (the binaries in the folder `zeppelin-web/node`)
|
||||
|
||||
If you want to compile the WebApplication only, you will have to simply run `mvn package` in this folder.
|
||||
```
|
||||
$ mvn package
|
||||
```
|
||||
|
||||
This will Download all the dependencies including node js and npm (you will find the binaries in the folder `zeppelin-web/node`).
|
||||
### Local Development
|
||||
|
||||
We are supposed to provide some **helper script** for __bower__ and __grunt__, but they are currently outdated, so you might want install them on your machine and use them instead.
|
||||
It is recommended to install node 6.0.0+ since Zeppelin uses 6.9.1+ (see [creationix/nvm](https://github.com/creationix/nvm))
|
||||
|
||||
### Configured environment
|
||||
All build commands are described in [package.json](./package.json)
|
||||
|
||||
Here are the basic commands to compile the WebApplication with a configured environment (Installed grunt, bower, npm)
|
||||
```sh
|
||||
# install required depepdencies and bower packages (only once)
|
||||
$ npm install
|
||||
|
||||
**Build the application for production**
|
||||
# build zeppelin-web for production
|
||||
$ npm run build
|
||||
|
||||
`./grunt build`
|
||||
# run frontend application only in dev mode (localhost:9000)
|
||||
# you need to run zeppelin backend instance also
|
||||
$ npm run start
|
||||
|
||||
**Run the application in dev mode**
|
||||
# execute tests
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
``./grunt serve``
|
||||
## Troubleshooting
|
||||
|
||||
This will launch a Zeppelin WebApplication on port **9000** that will update on code changes.
|
||||
(You will need to have Zeppelin running on the side)
|
||||
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
**git error**
|
||||
#### Git error
|
||||
|
||||
In case of the error `ECMDERR Failed to execute "git ls-remote --tags --heads git://xxxxx", exit code of #128`
|
||||
|
||||
change your git config with `git config --global url."https://".insteadOf git://`
|
||||
|
||||
**proxy issues**
|
||||
#### Proxy issues
|
||||
|
||||
Try to add to the `.bowerrc` file the following content:
|
||||
```
|
||||
"proxy" : "http://<host>:<port>",
|
||||
"https-proxy" : "http://<host>:<port>"
|
||||
```
|
||||
```
|
||||
|
||||
also try to add proxy info to npm install command:
|
||||
```
|
||||
```xml
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
|
|
@ -57,8 +63,8 @@ also try to add proxy info to npm install command:
|
|||
</execution>
|
||||
```
|
||||
|
||||
|
||||
and retry to build again.
|
||||
|
||||
## Contribute on Zeppelin Web
|
||||
|
||||
If you wish to help us and contribute to Zeppelin WebApplication, please look at the overall project [contribution guidelines](https://zeppelin.apache.org/contribution/contributions.html) and the more focused [Zeppelin WebApplication's documentation](https://zeppelin.apache.org/contribution/webapplication.html).
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
"node/node" "./node_modules/bower/bin/bower" "$@"
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
"node/node" "./node_modules/.bin/grunt" "$@"
|
||||
|
||||
|
|
@ -2,13 +2,20 @@
|
|||
"name": "zeppelin-web",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.0.0",
|
||||
"engines" : { "node" : ">=6.0.0" },
|
||||
"scripts": {
|
||||
"postinstall": "node_modules/.bin/bower install --silent",
|
||||
"build": "./node_modules/.bin/grunt build",
|
||||
"start": "./node_modules/.bin/grunt serve",
|
||||
"test": "./node_modules/.bin/grunt test"
|
||||
},
|
||||
"dependencies": {
|
||||
"grunt-angular-templates": "^0.5.7",
|
||||
"grunt-dom-munger": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.1.0",
|
||||
"bower": "1.7.2",
|
||||
"bower": "^1.8.0",
|
||||
"grunt": "^0.4.1",
|
||||
"grunt-cache-bust": "1.3.0",
|
||||
"grunt-cli": "^0.1.13",
|
||||
|
|
@ -26,17 +33,18 @@
|
|||
"grunt-goog-webfont-dl": "^0.1.2",
|
||||
"grunt-htmlhint": "^0.9.13",
|
||||
"grunt-jscs": "^2.1.0",
|
||||
"grunt-karma": "~0.8.3",
|
||||
"grunt-karma": "~2.0.0",
|
||||
"grunt-newer": "^0.7.0",
|
||||
"grunt-ng-annotate": "^0.10.0",
|
||||
"grunt-postcss": "^0.7.1",
|
||||
"grunt-svgmin": "^0.4.0",
|
||||
"grunt-usemin": "^2.1.1",
|
||||
"grunt-wiredep": "~2.0.0",
|
||||
"karma": "~0.12.23",
|
||||
"karma-coverage": "^0.5.1",
|
||||
"karma-jasmine": "~0.1.5",
|
||||
"karma-phantomjs-launcher": "~0.1.4",
|
||||
"jasmine-core": "^2.5.2",
|
||||
"karma": "~1.3.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-jasmine": "~1.0.2",
|
||||
"karma-phantomjs-launcher": "~1.0.2",
|
||||
"load-grunt-tasks": "^0.4.0",
|
||||
"time-grunt": "^0.3.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@
|
|||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>0.0.25</version>
|
||||
<version>1.3</version>
|
||||
<executions>
|
||||
|
||||
<execution>
|
||||
|
|
@ -99,8 +99,8 @@
|
|||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v0.12.13</nodeVersion>
|
||||
<npmVersion>2.15.0</npmVersion>
|
||||
<nodeVersion>v6.9.1</nodeVersion>
|
||||
<npmVersion>3.10.8</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
|
|
@ -109,36 +109,29 @@
|
|||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>bower install</id>
|
||||
<goals>
|
||||
<goal>bower</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>--allow-root install</arguments>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>grunt build</id>
|
||||
<id>npm build</id>
|
||||
<goals>
|
||||
<goal>grunt</goal>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>build</arguments>
|
||||
<arguments>run build</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>grunt test</id>
|
||||
<id>npm test</id>
|
||||
<goals>
|
||||
<goal>grunt</goal>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<arguments>test</arguments>
|
||||
<arguments>run test</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
|
|
|
|||
|
|
@ -360,87 +360,51 @@
|
|||
return noteCopy;
|
||||
};
|
||||
|
||||
var updateNote = function(note) {
|
||||
/** update Note name */
|
||||
if (note.name !== $scope.note.name) {
|
||||
console.log('change note name: %o to %o', $scope.note.name, note.name);
|
||||
$scope.note.name = note.name;
|
||||
}
|
||||
|
||||
$scope.note.config = note.config;
|
||||
$scope.note.info = note.info;
|
||||
|
||||
var newParagraphIds = note.paragraphs.map(function(x) {return x.id;});
|
||||
var oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
|
||||
|
||||
var numNewParagraphs = newParagraphIds.length;
|
||||
var numOldParagraphs = oldParagraphIds.length;
|
||||
|
||||
var paragraphToBeFocused;
|
||||
var focusedParagraph;
|
||||
for (var i = 0; i < $scope.note.paragraphs.length; i++) {
|
||||
var paragraphId = $scope.note.paragraphs[i].id;
|
||||
if (angular.element('#' + paragraphId + '_paragraphColumn_main').scope().paragraphFocused) {
|
||||
focusedParagraph = paragraphId;
|
||||
break;
|
||||
var addPara = function(paragraph, index) {
|
||||
$scope.note.paragraphs.splice(index, 0, paragraph);
|
||||
_.each($scope.note.paragraphs, function(para) {
|
||||
if (para.id === paragraph.id) {
|
||||
para.focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** add a new paragraph */
|
||||
if (numNewParagraphs > numOldParagraphs) {
|
||||
for (var index in newParagraphIds) {
|
||||
if (oldParagraphIds[index] !== newParagraphIds[index]) {
|
||||
$scope.note.paragraphs.splice(index, 0, note.paragraphs[index]);
|
||||
paragraphToBeFocused = note.paragraphs[index].id;
|
||||
break;
|
||||
}
|
||||
$scope.$broadcast('updateParagraph', {
|
||||
note: $scope.note, // pass the note object to paragraph scope
|
||||
paragraph: note.paragraphs[index]});
|
||||
}
|
||||
}
|
||||
|
||||
/** update or move paragraph */
|
||||
if (numNewParagraphs === numOldParagraphs) {
|
||||
for (var idx in newParagraphIds) {
|
||||
var newEntry = note.paragraphs[idx];
|
||||
if (oldParagraphIds[idx] === newParagraphIds[idx]) {
|
||||
$scope.$broadcast('updateParagraph', {
|
||||
note: $scope.note, // pass the note object to paragraph scope
|
||||
paragraph: newEntry});
|
||||
} else {
|
||||
// move paragraph
|
||||
var oldIdx = oldParagraphIds.indexOf(newParagraphIds[idx]);
|
||||
$scope.note.paragraphs.splice(oldIdx, 1);
|
||||
$scope.note.paragraphs.splice(idx, 0, newEntry);
|
||||
// rebuild id list since paragraph has moved.
|
||||
oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
|
||||
}
|
||||
|
||||
if (focusedParagraph === newParagraphIds[idx]) {
|
||||
paragraphToBeFocused = focusedParagraph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** remove paragraph */
|
||||
if (numNewParagraphs < numOldParagraphs) {
|
||||
for (var oldidx in oldParagraphIds) {
|
||||
if (oldParagraphIds[oldidx] !== newParagraphIds[oldidx]) {
|
||||
$scope.note.paragraphs.splice(oldidx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restore focus of paragraph
|
||||
for (var f = 0; f < $scope.note.paragraphs.length; f++) {
|
||||
if (paragraphToBeFocused === $scope.note.paragraphs[f].id) {
|
||||
$scope.note.paragraphs[f].focus = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var removePara = function(paragraphId) {
|
||||
var removeIdx;
|
||||
_.each($scope.note.paragraphs, function(para, idx) {
|
||||
if (para.id === paragraphId) {
|
||||
removeIdx = idx;
|
||||
}
|
||||
});
|
||||
return $scope.note.paragraphs.splice(removeIdx, 1);
|
||||
};
|
||||
|
||||
$scope.$on('addParagraph', function(event, paragraph, index) {
|
||||
addPara(paragraph, index);
|
||||
});
|
||||
|
||||
$scope.$on('removeParagraph', function(event, paragraphId) {
|
||||
removePara(paragraphId);
|
||||
});
|
||||
|
||||
$scope.$on('moveParagraph', function(event, paragraphId, newIdx) {
|
||||
var removedPara = removePara(paragraphId);
|
||||
if (removedPara && removedPara.length === 1) {
|
||||
addPara(removedPara[0], newIdx);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('updateNote', function(event, name, config, info) {
|
||||
/** update Note name */
|
||||
if (name !== $scope.note.name) {
|
||||
console.log('change note name to : %o', $scope.note.name);
|
||||
$scope.note.name = name;
|
||||
}
|
||||
$scope.note.config = config;
|
||||
$scope.note.info = info;
|
||||
initializeLookAndFeel();
|
||||
});
|
||||
|
||||
var getInterpreterBindings = function() {
|
||||
websocketMsgSrv.getInterpreterBindings($scope.note.id);
|
||||
};
|
||||
|
|
@ -856,8 +820,6 @@
|
|||
|
||||
if ($scope.note === null) {
|
||||
$scope.note = note;
|
||||
} else {
|
||||
updateNote(note);
|
||||
}
|
||||
initializeLookAndFeel();
|
||||
//open interpreter binding setting when there're none selected
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
-->
|
||||
|
||||
<a class="notebook-list-item" ng-if="note.id" href="#/notebook/{{note.id}}">
|
||||
<a class="notebook-list-item" ng-if="!note.children" href="#/notebook/{{note.id}}">
|
||||
<i style="font-size: 10px; margin-right: 5px;" class="icon-doc"></i>
|
||||
<span>{{noteName(note)}}</span>
|
||||
</a>
|
||||
<li ng-if="!note.id" ng-click="$event.stopPropagation()">
|
||||
<li ng-if="note.children" ng-click="$event.stopPropagation()">
|
||||
<expand-collapse>
|
||||
<div>
|
||||
<a class="notebook-list-item" href="javascript:void(0)">
|
||||
|
|
|
|||
|
|
@ -140,6 +140,14 @@
|
|||
$rootScope.$broadcast('configurationsInfo', data);
|
||||
} else if (op === 'INTERPRETER_SETTINGS') {
|
||||
$rootScope.$broadcast('interpreterSettings', data);
|
||||
} else if (op === 'PARAGRAPH_ADDED') {
|
||||
$rootScope.$broadcast('addParagraph', data.paragraph, data.index);
|
||||
} else if (op === 'PARAGRAPH_REMOVED') {
|
||||
$rootScope.$broadcast('removeParagraph', data.id);
|
||||
} else if (op === 'PARAGRAPH_MOVED') {
|
||||
$rootScope.$broadcast('moveParagraph', data.id, data.index);
|
||||
} else if (op === 'NOTE_UPDATED') {
|
||||
$rootScope.$broadcast('updateNote', data.name, data.config, data.info);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ describe('Controller: ParagraphCtrl', function() {
|
|||
|
||||
it('should call loadTableData() and getGraphMode() should return "table" when the result type is "TABLE"',
|
||||
function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').andCallFake(function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').and.callFake(function() {
|
||||
return 'TABLE';
|
||||
});
|
||||
spyOn(scope, 'setGraphMode');
|
||||
|
|
@ -71,7 +71,7 @@ describe('Controller: ParagraphCtrl', function() {
|
|||
});
|
||||
|
||||
it('should call renderHtml() when the result type is "HTML"', function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').andCallFake(function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').and.callFake(function() {
|
||||
return 'HTML';
|
||||
});
|
||||
spyOn(scope, 'renderHtml');
|
||||
|
|
@ -80,7 +80,7 @@ describe('Controller: ParagraphCtrl', function() {
|
|||
});
|
||||
|
||||
it('should call renderAngular() when the result type is "ANGULAR"', function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').andCallFake(function() {
|
||||
scope.getResultType = jasmine.createSpy('getResultType spy').and.callFake(function() {
|
||||
return 'ANGULAR';
|
||||
});
|
||||
spyOn(scope, 'renderAngular');
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
|
|||
+ "org.apache.zeppelin.flink.FlinkInterpreter,"
|
||||
+ "org.apache.zeppelin.python.PythonInterpreter,"
|
||||
+ "org.apache.zeppelin.python.PythonInterpreterPandasSql,"
|
||||
+ "org.apache.zeppelin.python.PythonCondaInterpreter,"
|
||||
+ "org.apache.zeppelin.ignite.IgniteInterpreter,"
|
||||
+ "org.apache.zeppelin.ignite.IgniteSqlInterpreter,"
|
||||
+ "org.apache.zeppelin.lens.LensInterpreter,"
|
||||
|
|
|
|||
|
|
@ -1381,9 +1381,9 @@ public class InterpreterFactory implements InterpreterGroupFactory {
|
|||
public Map<String, Object> getEditorSetting(String user, String noteId, String replName) {
|
||||
Interpreter intp = getInterpreter(user, noteId, replName);
|
||||
Map<String, Object> editor = DEFAULT_EDITOR;
|
||||
String defaultSettingName = getDefaultInterpreterSetting(noteId).getName();
|
||||
String group = StringUtils.EMPTY;
|
||||
try {
|
||||
String defaultSettingName = getDefaultInterpreterSetting(noteId).getName();
|
||||
List<InterpreterSetting> intpSettings = getInterpreterSettings(noteId);
|
||||
for (InterpreterSetting intpSetting : intpSettings) {
|
||||
String[] replNameSplit = replName.split("\\.");
|
||||
|
|
|
|||
|
|
@ -157,24 +157,25 @@ public class Paragraph extends Job implements Serializable, Cloneable {
|
|||
return null;
|
||||
}
|
||||
|
||||
String trimmed = text.trim();
|
||||
if (!trimmed.startsWith("%")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get script head
|
||||
int scriptHeadIndex = 0;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (Character.isWhitespace(ch) || ch == '(') {
|
||||
scriptHeadIndex = i;
|
||||
for (int i = 0; i < trimmed.length(); i++) {
|
||||
char ch = trimmed.charAt(i);
|
||||
if (Character.isWhitespace(ch) || ch == '(' || ch == '\n') {
|
||||
break;
|
||||
}
|
||||
scriptHeadIndex = i;
|
||||
}
|
||||
if (scriptHeadIndex == 0) {
|
||||
return null;
|
||||
}
|
||||
String head = text.substring(0, scriptHeadIndex);
|
||||
if (head.startsWith("%")) {
|
||||
return head.substring(1);
|
||||
} else {
|
||||
if (scriptHeadIndex < 1) {
|
||||
return null;
|
||||
}
|
||||
String head = text.substring(1, scriptHeadIndex + 1);
|
||||
return head;
|
||||
}
|
||||
|
||||
public String getScriptBody() {
|
||||
|
|
@ -190,10 +191,12 @@ public class Paragraph extends Job implements Serializable, Cloneable {
|
|||
if (magic == null) {
|
||||
return text;
|
||||
}
|
||||
if (magic.length() + 1 >= text.length()) {
|
||||
|
||||
String trimmed = text.trim();
|
||||
if (magic.length() + 1 >= trimmed.length()) {
|
||||
return "";
|
||||
}
|
||||
return text.substring(magic.length() + 1).trim();
|
||||
return trimmed.substring(magic.length() + 1).trim();
|
||||
}
|
||||
|
||||
public Interpreter getRepl(String name) {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@ public class Message {
|
|||
INTERPRETER_SETTINGS, // [s-c] interpreter settings
|
||||
ERROR_INFO, // [s-c] error information to be sent
|
||||
WATCHER, // [s-c] Change websocket to watcher mode.
|
||||
PARAGRAPH_ADDED, // [s-c] paragraph is added
|
||||
PARAGRAPH_REMOVED, // [s-c] paragraph deleted
|
||||
PARAGRAPH_MOVED, // [s-c] paragraph moved
|
||||
NOTE_UPDATED // [s-c] paragraph updated(name, config)
|
||||
}
|
||||
|
||||
public static final Message EMPTY = new Message(null);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,21 @@ public class ParagraphTest {
|
|||
assertEquals(text, Paragraph.getScriptBody(text));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replNameAndNoBody() {
|
||||
String text = "%md";
|
||||
assertEquals("md", Paragraph.getRequiredReplName(text));
|
||||
assertEquals("", Paragraph.getScriptBody(text));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void replSingleCharName() {
|
||||
String text = "%r a";
|
||||
assertEquals("r", Paragraph.getRequiredReplName(text));
|
||||
assertEquals("a", Paragraph.getScriptBody(text));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replNameEndsWithWhitespace() {
|
||||
String text = "%md\r\n###Hello";
|
||||
|
|
|
|||
Loading…
Reference in a new issue