Merge branch 'master' into jdbc-impersonation

This commit is contained in:
astroshim 2016-10-30 15:55:55 +09:00
commit a305eca1d0
51 changed files with 1452 additions and 108 deletions

View file

@ -16,6 +16,9 @@
"defaultValue": "19998",
"description": "Alluxio master port"
}
},
"editor": {
"editOnDblClick": false
}
}
]
]

View file

@ -4,7 +4,9 @@
"name": "angular",
"className": "org.apache.zeppelin.angular.AngularInterpreter",
"properties": {
},
"editor": {
"editOnDblClick": true
}
}
]

View file

@ -5,7 +5,9 @@
"className": "org.apache.zeppelin.beam.BeamInterpreter",
"defaultInterpreter": true,
"properties": {
},
"editor": {
"editOnDblClick": false
}
}
]

View file

@ -24,7 +24,8 @@
}
},
"editor": {
"language": "sql"
"language": "sql",
"editOnDblClick": false
}
}
]

View file

@ -190,6 +190,9 @@
"defaultValue": "true",
"description": "Cassandra socket TCP no delay. Default = true"
}
},
"editor": {
"editOnDblClick": false
}
}
]

View file

@ -34,7 +34,7 @@ Interpreters in the same InterpreterGroup can reference each other. For example,
[InterpreterSetting](https://github.com/apache/zeppelin/blob/master/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java) is configuration of a given [InterpreterGroup](https://github.com/apache/zeppelin/blob/master/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterGroup.java) and a unit of start/stop interpreter.
All Interpreters in the same InterpreterSetting are launched in a single, separate JVM process. The Interpreter communicates with Zeppelin engine via **[Thrift](https://github.com/apache/zeppelin/blob/master/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift)**.
In 'Separate Interpreter(scoped / isolated) for each note' mode which you can see at the **Interpreter Setting** menu when you create a new interpreter, new interpreter instance will be created per notebook. But it still runs on the same JVM while they're in the same InterpreterSettings.
In 'Separate Interpreter(scoped / isolated) for each note' mode which you can see at the **Interpreter Setting** menu when you create a new interpreter, new interpreter instance will be created per note. But it still runs on the same JVM while they're in the same InterpreterSettings.
## Make your own Interpreter
@ -48,7 +48,7 @@ There are three locations where you can store your interpreter group, name and o
{ZEPPELIN_INTERPRETER_DIR}/{YOUR_OWN_INTERPRETER_DIR}/interpreter-setting.json
```
Here is an example of `interpreter-setting.json` on your own interpreter. Note that if you don't specify editor object, your interpreter will use plain text mode for syntax highlighting.
Here is an example of `interpreter-setting.json` on your own interpreter.
```json
[
@ -71,7 +71,8 @@ Here is an example of `interpreter-setting.json` on your own interpreter. Note t
}, ...
},
"editor": {
"language": "your-syntax-highlight-language"
"language": "your-syntax-highlight-language",
"editOnDblClick": false
}
},
{
@ -98,15 +99,18 @@ The name of the interpreter is what you later write to identify a paragraph whic
some interpreter specific code...
```
## Programming Languages for Interpreter
If the interpreter uses a specific programming language (like Scala, Python, SQL), it is generally recommended to add a syntax highlighting supported for that to the notebook paragraph editor.
## Editor setting for Interpreter
You can add `editor` object to `interpreter-setting.json` file to specify paragraph editor settings.
To check out the list of languages supported, see the `mode-*.js` files under `zeppelin-web/bower_components/ace-builds/src-noconflict` or from [github.com/ajaxorg/ace-builds](https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict).
### Language
If the interpreter uses a specific programming language (like Scala, Python, SQL), it is generally recommended to add a syntax highlighting supported for that to the note paragraph editor.
To check out the list of languages supported, see the `mode-*.js` files under `zeppelin-web/bower_components/ace-builds/src-noconflict` or from [github.com/ajaxorg/ace-builds](https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict).
If you want to add a new set of syntax highlighting,
1. Add the `mode-*.js` file to <code>[zeppelin-web/bower.json](https://github.com/apache/zeppelin/blob/master/zeppelin-web/bower.json)</code> ( when built, <code>[zeppelin-web/src/index.html](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/index.html)</code> will be changed automatically. ).
2. Add `editor` object to `interpreter-setting.json` file. If you want to set your language to `java` for example, add:
1. Add the `mode-*.js` file to <code>[zeppelin-web/bower.json](https://github.com/apache/zeppelin/blob/master/zeppelin-web/bower.json)</code> (when built, <code>[zeppelin-web/src/index.html](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/index.html)</code> will be changed automatically).
2. Add `language` field to `editor` object. Note that if you don't specify language field, your interpreter will use plain text mode for syntax highlighting. Let's say you want to set your language to `java`, then add:
```
"editor": {
@ -114,6 +118,14 @@ If you want to add a new set of syntax highlighting,
}
```
### Edit on double click
If your interpreter uses mark-up language such as markdown or HTML, set `editOnDblClick` to `true` so that text editor opens on pargraph double click and closes on paragraph run. Otherwise set it to `false`.
```
"editor": {
"editOnDblClick": false
}
```
## Install your interpreter binary
Once you have built your interpreter, you can place it under the interpreter directory with all its dependencies.
@ -150,7 +162,7 @@ Now you are done and ready to use your interpreter.
## Use your interpreter
### 0.5.0
Inside of a notebook, `%[INTERPRETER_NAME]` directive will call your interpreter.
Inside of a note, `%[INTERPRETER_NAME]` directive will call your interpreter.
Note that the first interpreter configuration in zeppelin.interpreters will be the default one.
For example,
@ -163,7 +175,7 @@ println(a)
```
### 0.6.0 and later
Inside of a notebook, `%[INTERPRETER_GROUP].[INTERPRETER_NAME]` directive will call your interpreter.
Inside of a note, `%[INTERPRETER_GROUP].[INTERPRETER_NAME]` directive will call your interpreter.
You can omit either [INTERPRETER\_GROUP] or [INTERPRETER\_NAME]. If you omit [INTERPRETER\_NAME], then first available interpreter will be selected in the [INTERPRETER\_GROUP].
Likewise, if you skip [INTERPRETER\_GROUP], then [INTERPRETER\_NAME] will be chosen from default interpreter group.
@ -216,7 +228,7 @@ Checkout some interpreters released with Zeppelin by default.
We welcome contribution to a new interpreter. Please follow these few steps:
- First, check out the general contribution guide [here](https://zeppelin.apache.org/contribution/contributions.html).
- Follow the steps in [Make your own Interpreter](#make-your-own-interpreter) section above.
- Follow the steps in [Make your own Interpreter](#make-your-own-interpreter) section and [Editor setting for Interpreter](#editor-setting-for-interpreter) above.
- Add your interpreter as in the [Configure your interpreter](#configure-your-interpreter) section above; also add it to the example template [zeppelin-site.xml.template](https://github.com/apache/zeppelin/blob/master/conf/zeppelin-site.xml.template).
- Add tests! They are run by [Travis](https://travis-ci.org/apache/zeppelin) for all changes and it is important that they are self-contained.
- Include your interpreter as a module in [`pom.xml`](https://github.com/apache/zeppelin/blob/master/pom.xml).

View file

@ -25,7 +25,7 @@ limitations under the License.
## Text
By default, Apache Zeppelin prints interpreter responce as a plain text using `text` display system.
By default, Apache Zeppelin prints interpreter response as a plain text using `text` display system.
<img src="/assets/themes/zeppelin/img/screenshots/display_text.png" />
@ -41,7 +41,7 @@ With `%html` directive, Zeppelin treats your output as HTML
## Table
If you have data that row seprated by '\n' (newline) and column separated by '\t' (tab) with first row as header row, for example
If you have data that row separated by '\n' (newline) and column separated by '\t' (tab) with first row as header row, for example
<img src="/assets/themes/zeppelin/img/screenshots/display_table.png" />

View file

@ -55,7 +55,7 @@ Stable binary packages are available on the [Apache Zeppelin Download Page](http
If you downloaded the default package, just unpack it in a directory of your choice and you're ready to go. If you downloaded the *net-install* package, you should manually [install additional interpreters](../manual/interpreterinstallation.html) first. You can also install everything by running `./bin/install-interpreter.sh --all`.
After unpacking, jump to the [Starting Apache Zeppelin with Command Line](#starting-apache-zeppelin-with-command-line).
After unpacking, jump to the [Starting Apache Zeppelin from Command Line](#starting-apache-zeppelin-from-the-command-line).
### Building from Source
@ -178,7 +178,7 @@ chdir /usr/share/zeppelin
exec bin/zeppelin-daemon.sh upstart
```
## Next Steps:
## Next Steps
Congratulations, you have successfully installed Apache Zeppelin! Here are two next steps you might find useful:

View file

@ -28,6 +28,9 @@
"defaultValue": "10",
"description": "The size of the result set of a search query"
}
},
"editor": {
"editOnDblClick": false
}
}
]

View file

@ -22,6 +22,9 @@
"defaultValue": "1000",
"description": "Maximum number of lines of results fetched"
}
},
"editor": {
"editOnDblClick": false
}
}
]

View file

@ -34,7 +34,7 @@
<description>Zeppelin flink support</description>
<properties>
<flink.version>1.1.2</flink.version>
<flink.version>1.1.3</flink.version>
<flink.akka.version>2.3.7</flink.akka.version>
<scala.macros.version>2.0.1</scala.macros.version>
</properties>

View file

@ -18,7 +18,8 @@
}
},
"editor": {
"language": "scala"
"language": "scala",
"editOnDblClick": false
}
}
]

View file

@ -20,6 +20,9 @@
"defaultValue": "false",
"description": "Disable checks for unit and manual tests"
}
},
"editor": {
"editOnDblClick": false
}
}
]

View file

@ -66,7 +66,8 @@
}
},
"editor": {
"language": "sql"
"language": "sql",
"editOnDblClick": false
}
}
]

View file

@ -48,7 +48,8 @@
}
},
"editor": {
"language": "sql"
"language": "sql",
"editOnDblClick": false
}
}
]

View file

@ -40,6 +40,7 @@
<achilles.version>3.2.4-Zeppelin</achilles.version>
<assertj.version>1.7.0</assertj.version>
<mockito.version>1.9.5</mockito.version>
<livy.version>0.2.0</livy.version>
</properties>
<dependencies>
@ -106,8 +107,58 @@
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.cloudera.livy</groupId>
<artifactId>livy-integration-test</artifactId>
<version>${livy.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.cloudera.livy</groupId>
<artifactId>livy-test-lib</artifactId>
<version>${livy.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.cloudera.livy</groupId>
<artifactId>livy-core</artifactId>
<version>${livy.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<repositories>
<repository>
<id>ossrh</id>
<name>ossrh repository</name>
<url>https://oss.sonatype.org/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
@ -165,6 +216,61 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.16</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<java.io.tmpdir>${project.build.directory}/tmp</java.io.tmpdir>
</systemPropertyVariables>
<argLine>-Xmx2048m</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<!-- Cleans up files that tests append to (because we have two test plugins). -->
<execution>
<id>pre-test-clean</id>
<phase>generate-test-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete file="${project.build.directory}/unit-tests.log" quiet="true" />
<delete file="${project.build.directory}/jacoco.exec" quiet="true" />
<delete dir="${project.build.directory}/tmp" quiet="true" />
</target>
</configuration>
</execution>
<!-- Create the temp directory to be used by tests. -->
<execution>
<id>create-tmp-dir</id>
<phase>generate-test-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<mkdir dir="${project.build.directory}/tmp" />
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View file

@ -321,6 +321,7 @@ public class LivyHelper {
+ userSessionMap.get(context.getAuthenticationInfo().getUser())
+ "/statements/" + id,
"GET", null, context.getParagraphId());
LOGGER.debug("statement {} response: {}", id, json);
try {
Map jsonMap = gson.fromJson(json,
new TypeToken<Map>() {

View file

@ -94,7 +94,8 @@
}
},
"editor": {
"language": "scala"
"language": "scala",
"editOnDblClick": false
}
},
{
@ -115,7 +116,8 @@
}
},
"editor": {
"language": "sql"
"language": "sql",
"editOnDblClick": false
}
},
{
@ -125,7 +127,8 @@
"properties": {
},
"editor": {
"language": "python"
"language": "python",
"editOnDblClick": false
}
},
{
@ -135,7 +138,8 @@
"properties": {
},
"editor": {
"language": "r"
"language": "r",
"editOnDblClick": false
}
}
]

View file

@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Properties;
@ -63,14 +64,14 @@ public class LivyHelperTest {
livyHelper.property = properties;
livyHelper.paragraphHttpMap = new HashMap<>();
livyHelper.gson = new GsonBuilder().setPrettyPrinting().create();
livyHelper.LOGGER = LoggerFactory.getLogger(LivyHelper.class);
doReturn("{\"id\":1,\"state\":\"idle\",\"kind\":\"spark\",\"proxyUser\":\"null\",\"log\":[]}")
.when(livyHelper)
.executeHTTP(
livyHelper.property.getProperty("zeppelin.livy.url") + "/sessions",
"POST",
"{\"kind\": \"spark\", \"proxyUser\": \"null\"}",
"{\"kind\": \"spark\", \"conf\": {}, \"proxyUser\": null}",
null
);
@ -80,7 +81,7 @@ public class LivyHelperTest {
.executeHTTP(
livyHelper.property.getProperty("zeppelin.livy.url") + "/sessions/1/statements",
"POST",
"{\"code\": \"print(1)\" }",
"{\"code\": \"print(1)\"}",
null
);

View file

@ -0,0 +1,242 @@
/*
* 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.livy;
import com.cloudera.livy.test.framework.Cluster;
import com.cloudera.livy.test.framework.Cluster$;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterOutput;
import org.apache.zeppelin.interpreter.InterpreterOutputListener;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class LivyIntegrationTest {
private static Logger LOGGER = LoggerFactory.getLogger(LivyIntegrationTest.class);
private static Cluster cluster;
private static Properties properties;
@BeforeClass
public static void setUp() {
if (!checkPreCondition()) {
return;
}
cluster = Cluster$.MODULE$.get();
LOGGER.info("Starting livy at {}", cluster.livyEndpoint());
properties = new Properties();
properties.setProperty("zeppelin.livy.url", cluster.livyEndpoint());
properties.setProperty("zeppelin.livy.create.session.retries", "120");
properties.setProperty("zeppelin.livy.spark.sql.maxResult", "100");
}
@AfterClass
public static void tearDown() {
if (cluster != null) {
cluster.cleanUp();
}
}
public static boolean checkPreCondition() {
if (System.getenv("LIVY_HOME") == null) {
LOGGER.warn(("livy integration is skipped because LIVY_HOME is not set"));
return false;
}
if (System.getenv("SPARK_HOME") == null) {
LOGGER.warn(("livy integration is skipped because SPARK_HOME is not set"));
return false;
}
return true;
}
@Test
public void testSparkInterpreter() {
if (!checkPreCondition()) {
return;
}
LivySparkInterpreter sparkInterpreter = new LivySparkInterpreter(properties);
AuthenticationInfo authInfo = new AuthenticationInfo("user1");
MyInterpreterOutputListener outputListener = new MyInterpreterOutputListener();
InterpreterOutput output = new InterpreterOutput(outputListener);
InterpreterContext context = new InterpreterContext("noteId", "paragraphId", "title",
"text", authInfo, null, null, null, null, null, output);
sparkInterpreter.open();
InterpreterResult result = sparkInterpreter.interpret("sc.version", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
// test RDD api
outputListener.reset();
result = sparkInterpreter.interpret("sc.parallelize(1 to 10).sum()", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertTrue(outputListener.getOutputAppended().contains("Double = 55.0"));
// test DataFrame api
outputListener.reset();
sparkInterpreter.interpret("val sqlContext = new org.apache.spark.sql.SQLContext(sc)\n"
+ "import sqlContext.implicits._", context);
result = sparkInterpreter.interpret("val df=sqlContext.createDataFrame(Seq((\"hello\",20)))\n"
+ "df.collect()" , context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertTrue(outputListener.getOutputAppended()
.contains("Array[org.apache.spark.sql.Row] = Array([hello,20])"));
sparkInterpreter.interpret("df.registerTempTable(\"df\")", context);
// test LivySparkSQLInterpreter which share the same SparkContext with LivySparkInterpreter
outputListener.reset();
LivySparkSQLInterpreter sqlInterpreter = new LivySparkSQLInterpreter(properties);
sqlInterpreter.open();
result = sqlInterpreter.interpret("select * from df", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TABLE, result.type());
// TODO (zjffdu), \t at the end of each line is not necessary, it is a bug of LivySparkSQLInterpreter
assertEquals("_1\t_2\t\nhello\t20\t\n", result.message());
// single line comment
outputListener.reset();
String singleLineComment = "// my comment";
result = sparkInterpreter.interpret(singleLineComment, context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertNull(result.message());
// multiple line comment
outputListener.reset();
String multipleLineComment = "/* multiple \n" + "line \n" + "comment */";
result = sparkInterpreter.interpret(multipleLineComment, context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertNull(result.message());
// multi-line string
outputListener.reset();
String multiLineString = "val str = \"\"\"multiple\n" +
"line\"\"\"\n" +
"println(str)";
result = sparkInterpreter.interpret(multiLineString, context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertNull(result.message());
assertTrue(outputListener.getOutputAppended().contains("multiple\nline"));
// case class
outputListener.reset();
String caseClassCode = "case class Person(id:Int, \n" +
"name:String)\n" +
"val p=Person(1, \"name_a\")";
result = sparkInterpreter.interpret(caseClassCode, context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertNull(result.message());
assertTrue(outputListener.getOutputAppended().contains("defined class Person"));
// object class
outputListener.reset();
String objectClassCode = "object Person {}";
result = sparkInterpreter.interpret(objectClassCode, context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertNull(result.message());
assertTrue(outputListener.getOutputAppended().contains("defined module Person"));
}
@Test
public void testPySparkInterpreter() {
if (!checkPreCondition()) {
return;
}
LivyPySparkInterpreter pysparkInterpreter = new LivyPySparkInterpreter(properties);
AuthenticationInfo authInfo = new AuthenticationInfo("user1");
MyInterpreterOutputListener outputListener = new MyInterpreterOutputListener();
InterpreterOutput output = new InterpreterOutput(outputListener);
InterpreterContext context = new InterpreterContext("noteId", "paragraphId", "title",
"text", authInfo, null, null, null, null, null, output);
pysparkInterpreter.open();
InterpreterResult result = pysparkInterpreter.interpret("sc.version", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
// test RDD api
outputListener.reset();
result = pysparkInterpreter.interpret("sc.range(1, 10).sum()", context);
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertEquals("45", result.message());
// test DataFrame api
outputListener.reset();
pysparkInterpreter.interpret("from pyspark.sql import SQLContext\n"
+ "sqlContext = SQLContext(sc)", context);
result = pysparkInterpreter.interpret("df=sqlContext.createDataFrame([(\"hello\",20)])\n"
+ "df.collect()" , context);
assertTrue(result.message().contains("[Row(_1=u'hello', _2=20)]"));
assertEquals(InterpreterResult.Type.TEXT, result.type());
assertEquals(InterpreterResult.Code.SUCCESS, result.code());
}
@Test
public void testSparkRInterpreter() {
if (!checkPreCondition()) {
return;
}
// TODO (zjffdu), Livy's SparkRIntepreter has some issue, do it after livy-0.3 release.
}
public static class MyInterpreterOutputListener implements InterpreterOutputListener {
private StringBuilder outputAppended = new StringBuilder();
private StringBuilder outputUpdated = new StringBuilder();
@Override
public void onAppend(InterpreterOutput out, byte[] line) {
LOGGER.info("onAppend:" + new String(line));
outputAppended.append(new String(line));
}
@Override
public void onUpdate(InterpreterOutput out, byte[] output) {
LOGGER.info("onUpdate:" + new String(output));
outputUpdated.append(new String(output));
}
public String getOutputAppended() {
return outputAppended.toString();
}
public String getOutputUpdated() {
return outputUpdated.toString();
}
public void reset() {
outputAppended = new StringBuilder();
outputUpdated = new StringBuilder();
}
}
}

View file

@ -0,0 +1,24 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
log4j.rootLogger = INFO, stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%d] ({%t} %F[%M]:%L) - %m%n
log4j.logger.org.apache.zeppelin.livy=DEBUG

View file

@ -12,7 +12,8 @@
}
},
"editor": {
"language": "markdown"
"language": "markdown",
"editOnDblClick": true
}
}
]

View file

@ -18,7 +18,8 @@
}
},
"editor": {
"language": "pig"
"language": "pig",
"editOnDblClick": false
}
},
{
@ -40,7 +41,8 @@
}
},
"editor": {
"language": "pig"
"language": "pig",
"editOnDblClick": false
}
}
]

View file

@ -18,13 +18,19 @@
}
},
"editor": {
"language": "python"
"language": "python",
"editOnDblClick": false
}
},
{
"group": "python",
"name": "sql",
"className": "org.apache.zeppelin.python.PythonInterpreterPandasSql",
"properties": { }
"properties": {
},
"editor":{
"language": "sql",
"editOnDblClick": false
}
}
]

View file

@ -30,7 +30,8 @@
}
},
"editor": {
"language": "sh"
"language": "sh",
"editOnDblClick": false
}
}
]

View file

@ -56,7 +56,8 @@
}
},
"editor": {
"language": "scala"
"language": "scala",
"editOnDblClick": false
}
},
{
@ -90,7 +91,8 @@
}
},
"editor": {
"language": "sql"
"language": "sql",
"editOnDblClick": false
}
},
{
@ -112,7 +114,8 @@
}
},
"editor": {
"language": "scala"
"language": "scala",
"editOnDblClick": false
}
},
{
@ -128,7 +131,8 @@
}
},
"editor": {
"language": "python"
"language": "python",
"editOnDblClick": false
}
}
]

View file

@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.rest;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
import org.apache.zeppelin.notebook.repo.NotebookRepoWithSettings;
import org.apache.zeppelin.rest.message.NotebookRepoSettingsRequest;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.socket.NotebookServer;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.apache.zeppelin.utils.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* NoteRepo rest API endpoint.
*
*/
@Path("/notebook-repositories")
@Produces("application/json")
public class NotebookRepoRestApi {
private static final Logger LOG = LoggerFactory.getLogger(NotebookRepoRestApi.class);
private Gson gson = new Gson();
private NotebookRepoSync noteRepos;
private NotebookServer notebookWsServer;
public NotebookRepoRestApi() {}
public NotebookRepoRestApi(NotebookRepoSync noteRepos, NotebookServer notebookWsServer) {
this.noteRepos = noteRepos;
this.notebookWsServer = notebookWsServer;
}
/**
* List all notebook repository
*/
@GET
@ZeppelinApi
public Response listRepoSettings() {
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
LOG.info("Getting list of NoteRepo with Settings for user {}", subject.getUser());
List<NotebookRepoWithSettings> settings = noteRepos.getNotebookRepos(subject);
return new JsonResponse<>(Status.OK, "", settings).build();
}
/**
* Update a specific note repo.
*
* @param message
* @param settingId
* @return
*/
@PUT
@ZeppelinApi
public Response updateRepoSetting(String payload) {
if (StringUtils.isBlank(payload)) {
return new JsonResponse<>(Status.NOT_FOUND, "", Collections.emptyMap()).build();
}
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
NotebookRepoSettingsRequest newSettings = NotebookRepoSettingsRequest.EMPTY;
try {
newSettings = gson.fromJson(payload, NotebookRepoSettingsRequest.class);
} catch (JsonSyntaxException e) {
LOG.error("Cannot update notebook repo settings", e);
return new JsonResponse<>(Status.NOT_ACCEPTABLE, "",
ImmutableMap.of("error", "Invalid payload structure")).build();
}
if (NotebookRepoSettingsRequest.isEmpty(newSettings)) {
LOG.error("Invalid property");
return new JsonResponse<>(Status.NOT_ACCEPTABLE, "",
ImmutableMap.of("error", "Invalid payload")).build();
}
LOG.info("User {} is going to change repo setting", subject.getUser());
NotebookRepoWithSettings updatedSettings =
noteRepos.updateNotebookRepo(newSettings.name, newSettings.settings, subject);
if (!updatedSettings.isEmpty()) {
LOG.info("Broadcasting note list to user {}", subject.getUser());
notebookWsServer.broadcastReloadedNoteList(subject, null);
}
return new JsonResponse<>(Status.OK, "", updatedSettings).build();
}
}

View file

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.rest.message;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
/**
* Represent payload of a notebook repo settings.
*/
public class NotebookRepoSettingsRequest {
public static final NotebookRepoSettingsRequest EMPTY = new NotebookRepoSettingsRequest();
public String name;
public Map<String, String> settings;
public NotebookRepoSettingsRequest() {
name = StringUtils.EMPTY;
settings = Collections.emptyMap();
}
public boolean isEmpty() {
return this == EMPTY;
}
public static boolean isEmpty(NotebookRepoSettingsRequest repoSetting) {
if (repoSetting == null) {
return true;
}
return repoSetting.isEmpty();
}
}

View file

@ -22,9 +22,7 @@ import java.io.IOException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import javax.net.ssl.SSLContext;
import javax.servlet.DispatcherType;
import javax.ws.rs.core.Application;
@ -37,9 +35,16 @@ import org.apache.zeppelin.helium.HeliumApplicationFactory;
import org.apache.zeppelin.interpreter.InterpreterFactory;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
import org.apache.zeppelin.rest.*;
import org.apache.zeppelin.rest.ConfigurationsRestApi;
import org.apache.zeppelin.rest.CredentialRestApi;
import org.apache.zeppelin.rest.HeliumRestApi;
import org.apache.zeppelin.rest.InterpreterRestApi;
import org.apache.zeppelin.rest.LoginRestApi;
import org.apache.zeppelin.rest.NotebookRepoRestApi;
import org.apache.zeppelin.rest.NotebookRestApi;
import org.apache.zeppelin.rest.SecurityRestApi;
import org.apache.zeppelin.rest.ZeppelinRestApi;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.LuceneSearch;
import org.apache.zeppelin.search.SearchService;
@ -47,7 +52,12 @@ import org.apache.zeppelin.socket.NotebookServer;
import org.apache.zeppelin.user.Credentials;
import org.apache.zeppelin.utils.SecurityUtils;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
@ -73,8 +83,8 @@ public class ZeppelinServer extends Application {
private SchedulerFactory schedulerFactory;
private InterpreterFactory replFactory;
private NotebookRepo notebookRepo;
private SearchService noteSearchService;
private NotebookRepoSync notebookRepo;
private NotebookAuthorization notebookAuthorization;
private Credentials credentials;
private DependencyResolver depResolver;
@ -308,6 +318,9 @@ public class ZeppelinServer extends Application {
= new NotebookRestApi(notebook, notebookWsServer, noteSearchService);
singletons.add(notebookApi);
NotebookRepoRestApi notebookRepoApi = new NotebookRepoRestApi(notebookRepo, notebookWsServer);
singletons.add(notebookRepoApi);
HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook);
singletons.add(heliumApi);

View file

@ -429,5 +429,51 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
}
}
@Test
public void testEditOnDoubleClick() throws Exception {
if (!endToEndTestEnabled()) {
return;
}
try {
createNewNote();
Actions action = new Actions(driver);
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);
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(Keys.SHIFT + "3");
driver.findElement(By.xpath(getParagraphXPath(1) + "//textarea")).sendKeys(" abc");
runParagraph(1);
waitForParagraph(1, "FINISHED");
collector.checkThat("Markdown editor is hidden after run ",
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@ng-show, 'paragraph.config.editorHide')]")).isDisplayed(),
CoreMatchers.equalTo(false));
collector.checkThat("Markdown editor is shown after run ",
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@ng-show, 'paragraph.config.tableHide')]")).isDisplayed(),
CoreMatchers.equalTo(true));
// to check if editOnDblClick field is fetched correctly after refresh
driver.navigate().refresh();
waitForParagraph(1, "FINISHED");
action.doubleClick(driver.findElement(By.xpath(getParagraphXPath(1)))).perform();
ZeppelinITUtils.sleep(1000, false);
collector.checkThat("Markdown editor is shown after double click ",
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@ng-show, 'paragraph.config.editorHide')]")).isDisplayed(),
CoreMatchers.equalTo(true));
collector.checkThat("Markdown editor is hidden after double click ",
driver.findElement(By.xpath(getParagraphXPath(1) + "//div[contains(@ng-show, 'paragraph.config.tableHide')]")).isDisplayed(),
CoreMatchers.equalTo(false));
deleteTestNotebook(driver);
} catch (Exception e) {
handleException("Exception in ParagraphActionsIT while testEditOnDoubleClick ", e);
}
}
}

View file

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.rest;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
* NotebookRepo rest api test.
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class NotebookRepoRestApiTest extends AbstractTestRestApi {
Gson gson = new Gson();
AuthenticationInfo anonymous;
@BeforeClass
public static void init() throws Exception {
AbstractTestRestApi.startUp();
}
@AfterClass
public static void destroy() throws Exception {
AbstractTestRestApi.shutDown();
}
@Before
public void setUp() {
anonymous = new AuthenticationInfo("anonymous");
}
private List<Map<String, Object>> getListOfReposotiry() throws IOException {
GetMethod get = httpGet("/notebook-repositories");
Map<String, Object> responce = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {}.getType());
get.releaseConnection();
return (List<Map<String, Object>>) responce.get("body");
}
private void updateNotebookRepoWithNewSetting(String payload) throws IOException {
PutMethod put = httpPut("/notebook-repositories", payload);
int status = put.getStatusCode();
put.releaseConnection();
assertThat(status, is(200));
}
@Test public void ThatCanGetNotebookRepositoiesSettings() throws IOException {
List<Map<String, Object>> listOfRepositories = getListOfReposotiry();
assertThat(listOfRepositories.size(), is(not(0)));
}
@Test public void setNewDirectoryForLocalDirectory() throws IOException {
List<Map<String, Object>> listOfRepositories = getListOfReposotiry();
String localVfs = StringUtils.EMPTY;
String className = StringUtils.EMPTY;
for (int i = 0; i < listOfRepositories.size(); i++) {
if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) {
localVfs = (String) ((List<Map<String, Object>>)listOfRepositories.get(i).get("settings")).get(0).get("selected");
className = (String) listOfRepositories.get(i).get("className");
break;
}
}
if (StringUtils.isBlank(localVfs)) {
// no loval VFS set...
return;
}
String payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"/tmp/newDir\" } }";
updateNotebookRepoWithNewSetting(payload);
// Verify
listOfRepositories = getListOfReposotiry();
String updatedPath = StringUtils.EMPTY;
for (int i = 0; i < listOfRepositories.size(); i++) {
if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) {
updatedPath = (String) ((List<Map<String, Object>>)listOfRepositories.get(i).get("settings")).get(0).get("selected");
break;
}
}
assertThat(updatedPath, is("/tmp/newDir"));
// go back to normal
payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"" + localVfs + "\" } }";
updateNotebookRepoWithNewSetting(payload);
}
}

View file

@ -70,6 +70,11 @@
templateUrl: 'app/interpreter/interpreter.html',
controller: 'InterpreterCtrl'
})
.when('/notebookRepos', {
templateUrl: 'app/notebookRepos/notebookRepos.html',
controller: 'NotebookReposCtrl',
controllerAs: 'noterepo'
})
.when('/credential', {
templateUrl: 'app/credential/credential.html',
controller: 'CredentialCtrl'

View file

@ -139,6 +139,10 @@
// register mouseevent handler for focus paragraph
document.addEventListener('keydown', $scope.keyboardShortcut);
$scope.paragraphOnDoubleClick = function(paragraphId) {
$scope.$broadcast('doubleClickParagraph', paragraphId);
};
/** Remove the note and go back tot he main page */
/** TODO(anthony): In the nearly future, go back to the main page and telle to the dude that the note have been remove */
$scope.removeNote = function(noteId) {

View file

@ -113,7 +113,8 @@ limitations under the License.
ng-include src="'app/notebook/paragraph/paragraph.html'"
ng-class="{'paragraph-space box paragraph-margin': !asIframe, 'focused': paragraphFocused,
'lastEmptyParagraph': !paragraph.text && !paragraph.result}"
ng-hide="currentParagraph.config.tableHide && viewOnly">
ng-hide="currentParagraph.config.tableHide && viewOnly"
ng-dblclick="paragraphOnDoubleClick(currentParagraph.id);">
</div>
<div class="new-paragraph" ng-click="insertNew('below');" ng-hide="!$last || viewOnly || asIframe ">
<h4 class="plus-sign">&#43;</h4>

View file

@ -41,8 +41,9 @@
$scope.paragraph = null;
$scope.originalText = '';
$scope.editor = null;
$scope.magic = null;
var editorSetting = {};
var pastePercentSign = false;
var paragraphScope = $rootScope.$new(true, $rootScope);
// to keep backward compatibility
@ -300,6 +301,10 @@
data, $scope.paragraph.config, $scope.paragraph.settings.params);
$scope.originalText = angular.copy(data);
$scope.dirtyText = undefined;
if (editorSetting.editOnDblClick) {
closeEditorAndOpenTable();
}
};
$scope.saveParagraph = function() {
@ -414,6 +419,23 @@
commitParagraph($scope.paragraph.title, $scope.paragraph.text, newConfig, newParams);
};
var openEditorAndCloseTable = function() {
manageEditorAndTableState(false, true);
};
var closeEditorAndOpenTable = function() {
manageEditorAndTableState(true, false);
};
var manageEditorAndTableState = function(showEditor, showTable) {
var newParams = angular.copy($scope.paragraph.settings.params);
var newConfig = angular.copy($scope.paragraph.config);
newConfig.editorHide = showEditor;
newConfig.tableHide = showTable;
commitParagraph($scope.paragraph.title, $scope.paragraph.text, newConfig, newParams);
};
$scope.showTitle = function() {
var newParams = angular.copy($scope.paragraph.settings.params);
var newConfig = angular.copy($scope.paragraph.config);
@ -626,6 +648,12 @@
$scope.handleFocus(false);
});
$scope.editor.on('paste', function(e) {
if (e.text.startsWith('%')) {
pastePercentSign = true;
}
});
$scope.editor.getSession().on('change', function(e, editSession) {
autoAdjustEditorHeight(_editor.container.id);
});
@ -705,7 +733,7 @@
}
};
var getAndSetEditorSetting = function(session, interpreterName) {
var getEditorSetting = function(interpreterName) {
var deferred = $q.defer();
websocketMsgSrv.getEditorSetting($scope.paragraph.id, interpreterName);
$timeout(
@ -715,42 +743,51 @@
}
}
), 1000);
deferred.promise.then(function(editorSetting) {
if (!_.isEmpty(editorSetting.editor)) {
var mode = 'ace/mode/' + editorSetting.editor.language;
$scope.paragraph.config.editorMode = mode;
session.setMode(mode);
}
});
return deferred.promise;
};
var setEditorLanguage = function(session, language) {
var mode = 'ace/mode/';
mode += language;
$scope.paragraph.config.editorMode = mode;
session.setMode(mode);
};
var setParagraphMode = function(session, paragraphText, pos) {
// Evaluate the mode only if the the position is undefined
// or the first 30 characters of the paragraph have been modified
// or cursor position is at beginning of second line.(in case user hit enter after typing %magic)
if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30) || (pos.row === 1 && pos.column === 0)) {
if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30) ||
(pos.row === 1 && pos.column === 0) || pastePercentSign || $scope.paragraphFocused) {
// If paragraph loading, use config value if exists
if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) {
session.setMode($scope.paragraph.config.editorMode);
} else {
var magic;
// set editor mode to default interpreter syntax if paragraph text doesn't start with '%'
// TODO(mina): dig into the cause what makes interpreterBindings has no element
if (!paragraphText.startsWith('%') && ((typeof pos !== 'undefined') && pos.row === 0 && pos.column === 1) ||
(typeof pos === 'undefined') && $scope.$parent.interpreterBindings.length !== 0) {
magic = $scope.$parent.interpreterBindings[0].name;
getAndSetEditorSetting(session, magic);
} else {
var replNameRegexp = /%(.+?)\s/g;
var match = replNameRegexp.exec(paragraphText);
if (match && $scope.magic !== match[1]) {
magic = match[1].trim();
$scope.magic = magic;
getAndSetEditorSetting(session, magic);
}
var magic = getInterpreterName(paragraphText);
if (editorSetting.magic !== magic) {
editorSetting.magic = magic;
getEditorSetting(magic)
.then(function(setting) {
setEditorLanguage(session, setting.editor.language);
_.merge(editorSetting, setting.editor);
});
}
}
}
pastePercentSign = false;
};
var getInterpreterName = function(paragraphText) {
var intpNameRegexp = /%(.+?)\s/g;
var match = intpNameRegexp.exec(paragraphText);
if (match) {
return match[1].trim();
// get default interpreter name if paragraph text doesn't start with '%'
// TODO(mina): dig into the cause what makes interpreterBindings to have no element
} else if ($scope.$parent.interpreterBindings.length !== 0) {
return $scope.$parent.interpreterBindings[0].name;
}
return '';
};
var autoAdjustEditorHeight = function(id) {
@ -1070,6 +1107,7 @@
var setD3Chart = function(type, data, refresh) {
if (!$scope.chart[type]) {
var chart = nv.models[type]();
chart._options.noData = 'Invalid Data, check graph settings';
$scope.chart[type] = chart;
}
@ -1086,7 +1124,6 @@
$scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);});
$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) {
var tooltipContent = '<h3>' + key + '</h3>';
@ -2459,6 +2496,23 @@
}
});
$scope.$on('doubleClickParagraph', function(event, paragraphId) {
if ($scope.paragraph.id === paragraphId && editorSetting.editOnDblClick) {
var deferred = $q.defer();
openEditorAndCloseTable();
$timeout(
$scope.$on('updateParagraph', function(event, data) {
deferred.resolve(data);
}
), 1000);
deferred.promise.then(function(data) {
$scope.editor.focus();
$scope.goToEnd();
});
}
});
$scope.$on('runParagraph', function(event) {
$scope.runParagraph($scope.editor.getValue());
});

View file

@ -67,8 +67,7 @@ limitations under the License.
class="executionTime" ng-bind-html="getExecutionTime()">
</div>
<div ng-if = "paragraph.status === 'RUNNING'" class = "paragraphFooterElapsed">
<div
id="{{paragraph.id}}_elapsedTime"
<div id="{{paragraph.id}}_elapsedTime"
class="elapsedTime" ng-bind-html="getElapsedTime()">
</div>
</div>

View file

@ -0,0 +1,91 @@
/*
* 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';
(function() {
angular.module('zeppelinWebApp').controller('NotebookReposCtrl', NotebookReposCtrl);
NotebookReposCtrl.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
function NotebookReposCtrl($http, baseUrlSrv, ngToast) {
var vm = this;
vm.notebookRepos = [];
vm.showDropdownSelected = showDropdownSelected;
vm.saveNotebookRepo = saveNotebookRepo;
_init();
// Public functions
function saveNotebookRepo(valueform, repo, data) {
console.log('data %o', data);
$http.put(baseUrlSrv.getRestApiBase() + '/notebook-repositories', {
'name': repo.className,
'settings': data
}).success(function(data) {
var index = _.findIndex(vm.notebookRepos, {'className': repo.className});
if (index >= 0) {
vm.notebookRepos[index] = data.body;
console.log('repos %o, data %o', vm.notebookRepos, data.body);
}
valueform.$show();
}).error(function() {
ngToast.danger({
content: 'We couldn\'t save that NotebookRepo\'s settings',
verticalPosition: 'bottom',
timeout: '3000'
});
valueform.$show();
});
return 'manual';
}
function showDropdownSelected(setting) {
var index = _.findIndex(setting.value, {'value': setting.selected});
if (index < 0) {
return 'No value';
} else {
return setting.value[index].name;
}
}
// Private functions
function _getInterpreterSettings() {
$http.get(baseUrlSrv.getRestApiBase() + '/notebook-repositories')
.success(function(data, status, headers, config) {
vm.notebookRepos = data.body;
console.log('ya notebookRepos %o', vm.notebookRepos);
}).error(function(data, status, headers, config) {
if (status === 401) {
ngToast.danger({
content: 'You don\'t have permission on this page',
verticalPosition: 'bottom',
timeout: '3000'
});
setTimeout(function() {
window.location.replace('/');
}, 3000);
}
console.log('Error %o %o', status, data.message);
});
}
function _init() {
_getInterpreterSettings();
};
}
})();

View file

@ -0,0 +1,98 @@
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="interpreterHead">
<div class="header">
<div class="row">
<div class="col-md-12">
<h3 class="new_h3">
Notebook Repos
</h3>
</div>
</div>
<div class="row">
<div class="col-md-12">
Manage your Notebook Repositories' settings.
</div>
</div>
</div>
</div>
<div class="box width-full"
ng-repeat="repo in noterepo.notebookRepos | orderBy: 'name'">
<div id="{{repo.name | lowercase}}">
<div class="row interpreter">
<div class="col-md-12">
<h3 class="interpreter-title">{{repo.name}}</h3>
<span style="float:right" ng-show="repo.settings.length > 0">
<button class="btn btn-default btn-xs"
ng-click="valueform.$show();">
<span class="fa fa-pencil"></span> edit</button>
</span>
</div>
</div>
<div class="row interpreter">
<div class="col-md-12" ng-show="repo.settings.length > 0">
<h5>Settings</h5>
<table class="table table-striped">
<thead>
<tr>
<th style="width:40%">name</th>
<th style="width:60%">value</th>
</tr>
</thead>
<tr ng-repeat="setting in repo.settings" >
<td ng-bind="setting.name"></td>
<td>
<span class="btn-group">
<span ng-show="setting.type === 'DROPDOWN'">
<span editable-select="setting.selected"
e-name="{{setting.name}}"
e-ng-options="s.value as s.name for s in setting.value"
class="selectpicker" ng-disabled="!valueform.$visible" e-form="valueform">
{{noterepo.showDropdownSelected(setting)}}
</span>
</span>
<span ng-show="setting.type === 'INPUT'">
<span editable-textarea="setting.selected" e-name="{{setting.name}}" e-form="valueform" e-msd-elastic e-cols="100">
{{setting.selected | breakFilter}}
</span>
</span>
</span>
</td>
</tr>
</table>
</div>
</div>
<span style="float:right" ng-show="valueform.$visible">
<form editable-form name="valueform"
onbeforesave="noterepo.saveNotebookRepo(valueform, repo, $data)"
ng-show="valueform.$visible">
<button type="submit" class="btn btn-primary btn-xs">
<span class="fa fa-check"></span> Save
</button>
<button type="button" class="btn btn-default btn-xs"
ng-disabled="valueform.$waiting"
ng-click="valueform.$cancel();">
<span class="fa fa-remove"></span> Cancel
</button>
</form>
</span>
<div class="row interpreter">
<div ng-show="repo.settings.length === 0 || valueform.$hidden" class="col-md-12 gray40-message">
<em>Currently there are no settings for this Notebook Repository</em>
</div>
</div>
</div>
</div>

View file

@ -86,6 +86,7 @@ limitations under the License.
<li><a href="" data-toggle="modal" data-target="#aboutModal">About Zeppelin</a></li>
<li role="separator" style="margin: 5px 0;" class="divider"></li>
<li><a href="#/interpreter">Interpreter</a></li>
<li><a href="#/notebookRepos">Notebook Repos</a></li>
<li><a href="#/credential">Credential</a></li>
<li><a href="#/configuration">Configuration</a></li>
<li ng-if="ticket.principal && ticket.principal !== 'anonymous'" role="separator" style="margin: 5px 0;" class="divider"></li>

View file

@ -162,6 +162,7 @@ limitations under the License.
<script src="app/configuration/configuration.controller.js"></script>
<script src="app/notebook/paragraph/paragraph.controller.js"></script>
<script src="app/search/result-list.controller.js"></script>
<script src="app/notebookRepos/notebookRepos.controller.js"></script>
<script src="components/arrayOrderingSrv/arrayOrdering.service.js"></script>
<script src="components/navbar/navbar.controller.js"></script>
<script src="components/ngescape/ngescape.directive.js"></script>

View file

@ -127,6 +127,10 @@ public class InterpreterFactory implements InterpreterGroupFactory {
private Interpreter devInterpreter;
private static final Map<String, Object> DEFAULT_EDITOR = ImmutableMap.of(
"language", (Object) "text",
"editOnDblClick", false);
public InterpreterFactory(ZeppelinConfiguration conf,
AngularObjectRegistryListener angularObjectRegistryListener,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
@ -429,11 +433,15 @@ public class InterpreterFactory implements InterpreterGroupFactory {
String className) {
List<InterpreterInfo> intpInfos = intpSetting.getInterpreterInfos();
for (InterpreterInfo intpInfo : intpInfos) {
if (className.equals(intpInfo.getClassName())) {
if (intpInfo.getEditor() == null) {
break;
}
return intpInfo.getEditor();
}
}
return ImmutableMap.of("language", (Object) "text");
return DEFAULT_EDITOR;
}
private void loadInterpreterDependencies(final InterpreterSetting setting) {
@ -1349,9 +1357,7 @@ 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 = Maps.newHashMap(
ImmutableMap.<String, Object>builder()
.put("language", "text").build());
Map<String, Object> editor = DEFAULT_EDITOR;
String defaultSettingName = getDefaultInterpreterSetting(noteId).getName();
String group = StringUtils.EMPTY;
try {
@ -1372,7 +1378,7 @@ public class InterpreterFactory implements InterpreterGroupFactory {
}
}
} catch (NullPointerException e) {
logger.warn("Couldn't get interpreter editor language");
logger.warn("Couldn't get interpreter editor setting");
}
return editor;
}

View file

@ -17,11 +17,19 @@
package org.apache.zeppelin.notebook.repo;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.file.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
@ -33,12 +41,16 @@ import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.file.CloudFile;
import com.microsoft.azure.storage.file.CloudFileClient;
import com.microsoft.azure.storage.file.CloudFileDirectory;
import com.microsoft.azure.storage.file.CloudFileShare;
import com.microsoft.azure.storage.file.ListFileItem;
/**
* Azure storage backend for notebooks
@ -227,4 +239,15 @@ public class AzureNotebookRepo implements NotebookRepo {
// Auto-generated method stub
return null;
}
@Override
public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
LOG.warn("Method not implemented");
return Collections.emptyList();
}
@Override
public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
LOG.warn("Method not implemented");
}
}

View file

@ -19,6 +19,7 @@ package org.apache.zeppelin.notebook.repo;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.notebook.Note;
@ -100,6 +101,22 @@ public interface NotebookRepo {
*/
@ZeppelinApi public List<Revision> revisionHistory(String noteId, AuthenticationInfo subject);
/**
* Get NotebookRepo settings got the given user.
*
* @param subject
* @return
*/
@ZeppelinApi public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject);
/**
* update notebook repo settings.
*
* @param settings
* @param subject
*/
@ZeppelinApi public void updateSettings(Map<String, String> settings, AuthenticationInfo subject);
/**
* Represents the 'Revision' a point in life of the notebook
*/

View file

@ -0,0 +1,44 @@
/*
* 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.notebook.repo;
import java.util.List;
import java.util.Map;
/**
* Notebook repo settings. This represent a structure of a notebook repo settings that will mostly
* used in the frontend.
*
*/
public class NotebookRepoSettingsInfo {
/**
* Type of value, It can be text or list.
*/
public enum Type {
INPUT, DROPDOWN
}
public static NotebookRepoSettingsInfo newInstance() {
return new NotebookRepoSettingsInfo();
}
public Type type;
public List<Map<String, String>> value;
public String selected;
public String name;
}

View file

@ -24,7 +24,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -39,6 +38,8 @@ import org.apache.zeppelin.user.AuthenticationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
/**
* Notebook repository sync with remote storage
*/
@ -109,6 +110,39 @@ public class NotebookRepoSync implements NotebookRepo {
}
}
public List<NotebookRepoWithSettings> getNotebookRepos(AuthenticationInfo subject) {
List<NotebookRepoWithSettings> reposSetting = Lists.newArrayList();
NotebookRepoWithSettings repoWithSettings;
for (NotebookRepo repo : repos) {
repoWithSettings = NotebookRepoWithSettings
.builder(repo.getClass().getSimpleName())
.className(repo.getClass().getName())
.settings(repo.getSettings(subject))
.build();
reposSetting.add(repoWithSettings);
}
return reposSetting;
}
public NotebookRepoWithSettings updateNotebookRepo(String name, Map<String, String> settings,
AuthenticationInfo subject) {
NotebookRepoWithSettings updatedSettings = NotebookRepoWithSettings.EMPTY;
for (NotebookRepo repo : repos) {
if (repo.getClass().getName().equals(name)) {
repo.updateSettings(settings, subject);
updatedSettings = NotebookRepoWithSettings
.builder(repo.getClass().getSimpleName())
.className(repo.getClass().getName())
.settings(repo.getSettings(subject))
.build();
break;
}
}
return updatedSettings;
}
/**
* Lists Notebooks from the first repository
*/
@ -444,4 +478,24 @@ public class NotebookRepoSync implements NotebookRepo {
}
return revisions;
}
@Override
public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
List<NotebookRepoSettingsInfo> repoSettings = Collections.emptyList();
try {
repoSettings = getRepo(0).getSettings(subject);
} catch (IOException e) {
LOG.error("Cannot get notebook repo settings", e);
}
return repoSettings;
}
@Override
public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
try {
getRepo(0).updateSettings(settings, subject);
} catch (IOException e) {
LOG.error("Cannot update notebook repo settings", e);
}
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.notebook.repo;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang.StringUtils;
/**
* Representation of a notebook repo with settings. This is mostly a Wrapper around notebook repo
* information plus settings.
*/
public class NotebookRepoWithSettings {
public static final NotebookRepoWithSettings EMPTY =
NotebookRepoWithSettings.builder(StringUtils.EMPTY).build();
public String name;
public String className;
public List<NotebookRepoSettingsInfo> settings;
private NotebookRepoWithSettings() {}
public static Builder builder(String name) {
return new Builder(name);
}
private NotebookRepoWithSettings(Builder builder) {
name = builder.name;
className = builder.className;
settings = builder.settings;
}
public boolean isEmpty() {
return this.equals(EMPTY);
}
/**
* Simple builder :).
*/
public static class Builder {
private final String name;
private String className = StringUtils.EMPTY;
private List<NotebookRepoSettingsInfo> settings = Collections.emptyList();
public Builder(String name) {
this.name = name;
}
public NotebookRepoWithSettings build() {
return new NotebookRepoWithSettings(this);
}
public Builder className(String className) {
this.className = className;
return this;
}
public Builder settings(List<NotebookRepoSettingsInfo> settings) {
this.settings = settings;
return this;
}
}
}

View file

@ -23,14 +23,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3EncryptionClient;
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
@ -45,10 +43,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3EncryptionClient;
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.PutObjectRequest;
@ -270,4 +272,15 @@ public class S3NotebookRepo implements NotebookRepo {
// Auto-generated method stub
return null;
}
@Override
public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
LOG.warn("Method not implemented");
return Collections.emptyList();
}
@Override
public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
LOG.warn("Method not implemented");
}
}

View file

@ -23,11 +23,14 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemManager;
@ -40,13 +43,14 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.notebook.ApplicationState;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.NoteInfo;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.NotebookImportDeserializer;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.scheduler.Job.Status;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -54,7 +58,7 @@ import com.google.gson.GsonBuilder;
*
*/
public class VFSNotebookRepo implements NotebookRepo {
Logger logger = LoggerFactory.getLogger(VFSNotebookRepo.class);
private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepo.class);
private FileSystemManager fsManager;
private URI filesystemRoot;
@ -62,12 +66,15 @@ public class VFSNotebookRepo implements NotebookRepo {
public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException {
this.conf = conf;
setNotebookDirectory(conf.getNotebookDir());
}
private void setNotebookDirectory(String notebookDirPath) throws IOException {
try {
if (conf.isWindowsPath(conf.getNotebookDir())) {
filesystemRoot = new File(conf.getNotebookDir()).toURI();
if (conf.isWindowsPath(notebookDirPath)) {
filesystemRoot = new File(notebookDirPath).toURI();
} else {
filesystemRoot = new URI(conf.getNotebookDir());
filesystemRoot = new URI(notebookDirPath);
}
} catch (URISyntaxException e1) {
throw new IOException(e1);
@ -76,7 +83,7 @@ public class VFSNotebookRepo implements NotebookRepo {
if (filesystemRoot.getScheme() == null) { // it is local path
try {
this.filesystemRoot = new URI(new File(
conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath());
conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath());
} catch (URISyntaxException e) {
throw new IOException(e);
}
@ -85,11 +92,15 @@ public class VFSNotebookRepo implements NotebookRepo {
fsManager = VFS.getManager();
FileObject file = fsManager.resolveFile(filesystemRoot.getPath());
if (!file.exists()) {
logger.info("Notebook dir doesn't exist, create.");
LOG.info("Notebook dir doesn't exist, create on is {}.", file.getName());
file.createFolder();
}
}
private String getNotebookDirPath() {
return filesystemRoot.getPath().toString();
}
private String getPath(String path) {
if (path == null || path.trim().length() == 0) {
return filesystemRoot.toString();
@ -141,7 +152,7 @@ public class VFSNotebookRepo implements NotebookRepo {
infos.add(info);
}
} catch (Exception e) {
logger.error("Can't read note " + f.getName().toString(), e);
LOG.error("Can't read note " + f.getName().toString(), e);
}
}
@ -285,4 +296,42 @@ public class VFSNotebookRepo implements NotebookRepo {
return null;
}
@Override
public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
NotebookRepoSettingsInfo repoSetting = NotebookRepoSettingsInfo.newInstance();
List<NotebookRepoSettingsInfo> settings = Lists.newArrayList();
repoSetting.name = "Notebook Path";
repoSetting.type = NotebookRepoSettingsInfo.Type.INPUT;
repoSetting.value = Collections.emptyList();
repoSetting.selected = getNotebookDirPath();
settings.add(repoSetting);
return settings;
}
@Override
public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
if (settings == null || settings.isEmpty()) {
LOG.error("Cannot update {} with empty settings", this.getClass().getName());
return;
}
String newNotebookDirectotyPath = StringUtils.EMPTY;
if (settings.containsKey("Notebook Path")) {
newNotebookDirectotyPath = settings.get("Notebook Path");
}
if (StringUtils.isBlank(newNotebookDirectotyPath)) {
LOG.error("Notebook path is invalid");
return;
}
LOG.warn("{} will change notebook dir from {} to {}",
subject.getUser(), getNotebookDirPath(), newNotebookDirectotyPath);
try {
setNotebookDirectory(newNotebookDirectotyPath);
} catch (IOException e) {
LOG.error("Cannot update notebook directory", e);
}
}
}

View file

@ -21,12 +21,14 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.NoteInfo;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.NotebookRepoSettingsInfo;
import org.apache.zeppelin.notebook.repo.zeppelinhub.rest.ZeppelinhubRestApiHandler;
import org.apache.zeppelin.notebook.repo.zeppelinhub.websocket.Client;
import org.apache.zeppelin.user.AuthenticationInfo;
@ -234,4 +236,15 @@ public class ZeppelinHubRepo implements NotebookRepo {
return history;
}
@Override
public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
LOG.warn("Method not implemented");
return Collections.emptyList();
}
@Override
public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
LOG.warn("Method not implemented");
}
}

View file

@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
@ -31,12 +32,13 @@ import org.apache.zeppelin.dep.DependencyResolver;
import org.apache.zeppelin.interpreter.InterpreterFactory;
import org.apache.zeppelin.interpreter.InterpreterOption;
import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
import org.apache.zeppelin.notebook.*;
import org.apache.zeppelin.notebook.repo.zeppelinhub.security.Authentication;
import org.apache.zeppelin.scheduler.JobListener;
import org.apache.zeppelin.notebook.JobListenerFactory;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.ParagraphJobListener;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.search.LuceneSearch;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.After;
import org.junit.Before;
@ -44,6 +46,8 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
public class VFSNotebookRepoTest implements JobListenerFactory {
private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepoTest.class);
private ZeppelinConfiguration conf;
@ -141,6 +145,24 @@ public class VFSNotebookRepoTest implements JobListenerFactory {
assertEquals(note.getName(), "SaveTest");
notebookRepo.remove(note.getId(), null);
}
@Test
public void testUpdateSettings() throws IOException {
AuthenticationInfo subject = new AuthenticationInfo("anonymous");
File tmpDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
Map<String, String> settings = ImmutableMap.of("Notebook Path", tmpDir.getAbsolutePath());
List<NotebookRepoSettingsInfo> repoSettings = notebookRepo.getSettings(subject);
String originalDir = repoSettings.get(0).selected;
notebookRepo.updateSettings(settings, subject);
repoSettings = notebookRepo.getSettings(subject);
assertEquals(repoSettings.get(0).selected, tmpDir.getAbsolutePath());
// restaure
notebookRepo.updateSettings(ImmutableMap.of("Notebook Path", originalDir), subject);
FileUtils.deleteQuietly(tmpDir);
}
class NotebookWriter implements Runnable {
Note note;