mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge branch 'master' into jdbc-impersonation
This commit is contained in:
commit
a305eca1d0
51 changed files with 1452 additions and 108 deletions
|
|
@ -16,6 +16,9 @@
|
|||
"defaultValue": "19998",
|
||||
"description": "Alluxio master port"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
"name": "angular",
|
||||
"className": "org.apache.zeppelin.angular.AngularInterpreter",
|
||||
"properties": {
|
||||
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
"className": "org.apache.zeppelin.beam.BeamInterpreter",
|
||||
"defaultInterpreter": true,
|
||||
"properties": {
|
||||
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "sql"
|
||||
"language": "sql",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -190,6 +190,9 @@
|
|||
"defaultValue": "true",
|
||||
"description": "Cassandra socket TCP no delay. Default = true"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
"defaultValue": "10",
|
||||
"description": "The size of the result set of a search query"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
"defaultValue": "1000",
|
||||
"description": "Maximum number of lines of results fetched"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "scala"
|
||||
"language": "scala",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@
|
|||
"defaultValue": "false",
|
||||
"description": "Disable checks for unit and manual tests"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "sql"
|
||||
"language": "sql",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "sql"
|
||||
"language": "sql",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
106
livy/pom.xml
106
livy/pom.xml
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
livy/src/test/resources/log4j.properties
Normal file
24
livy/src/test/resources/log4j.properties
Normal 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
|
||||
|
|
@ -12,7 +12,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "markdown"
|
||||
"language": "markdown",
|
||||
"editOnDblClick": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "pig"
|
||||
"language": "pig",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -40,7 +41,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "pig"
|
||||
"language": "pig",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"language": "sh"
|
||||
"language": "sh",
|
||||
"editOnDblClick": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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">+</h4>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
98
zeppelin-web/src/app/notebookRepos/notebookRepos.html
Normal file
98
zeppelin-web/src/app/notebookRepos/notebookRepos.html
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue