Merge branch 'master' of https://github.com/apache/incubator-zeppelin into markdownDocUpdate

This commit is contained in:
Renjith Kamath 2015-11-25 10:56:55 +05:30
commit 95442b4d40
24 changed files with 1043 additions and 195 deletions

172
README.md
View file

@ -49,133 +49,109 @@ _Notes:_
If you want to build Zeppelin from the source, please first clone this repository, then:
```
mvn clean package -DskipTests
mvn clean package -DskipTests [Options]
```
To build with a specific Spark version, Hadoop version or specific features, define one or more of the `spark`, `pyspark`, `hadoop` and `yarn` profiles, such as:
Each Interpreter requires different Options.
#### Spark Interpreter
To build with a specific Spark version, Hadoop version or specific features, define one or more of the following profiles and options:
##### -Pspark-[version]
Set spark major version
Available profiles are
```
-Pspark-1.5 [Version to run in local spark mode]
-Ppyspark [optional: enable PYTHON support in spark via the %pyspark interpreter]
-Pyarn [optional: enable YARN support]
-Dhadoop.version=2.2.0 [hadoop distribution]
-Phadoop-2.2 [hadoop version]
-Pspark-1.5
-Pspark-1.4
-Pspark-1.3
-Pspark-1.2
-Pspark-1.1
-Pcassandra-spark-1.5
-Pcassandra-spark-1.4
-Pcassandra-spark-1.3
-Pcassandra-spark-1.2
-Pcassandra-spark-1.1
```
Currently, final/full distributions run with:
minor version can be adjusted by `-Dspark.version=x.x.x`
##### -Phadoop-[version]
set hadoop major version
Available profiles are
```
mvn clean package -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
-Phadoop-0.23
-Phadoop-1
-Phadoop-2.2
-Phadoop-2.3
-Phadoop-2.4
-Phadoop-2.6
```
Spark 1.5.x
minor version can be adjusted by `-Dhadoop.version=x.x.x`
```
mvn clean package -Pspark-1.5 -Dhadoop.version=2.2.0 -Phadoop-2.2 -DskipTests
```
Spark 1.4.x
##### -Pyarn (optional)
```
mvn clean package -Pspark-1.4 -Dhadoop.version=2.2.0 -Phadoop-2.2 -DskipTests
```
Spark 1.3.x
enable YARN support for local mode
```
mvn clean package -Pspark-1.3 -Dhadoop.version=2.2.0 -Phadoop-2.2 -DskipTests
```
Spark 1.2.x
```
mvn clean package -Pspark-1.2 -Dhadoop.version=2.2.0 -Phadoop-2.2 -DskipTests
```
Spark 1.1.x
##### -Ppyspark (optional)
```
mvn clean package -Pspark-1.1 -Dhadoop.version=2.2.0 -Phadoop-2.2 -DskipTests
```
CDH 5.X
enable PySpark support for local mode
##### -Pvendor-repo (optional)
enable 3rd party vendor repository (cloudera)
##### -Pmapr[version] (optional)
```
mvn clean package -Pspark-1.2 -Dhadoop.version=2.5.0-cdh5.3.0 -Phadoop-2.4 -DskipTests
```
For the MapR Hadoop Distribution, these profiles will handle the Hadoop version. As MapR allows different versions
of Spark to be installed, you should specify which version of Spark is installed on the cluster by adding a Spark profile (-Pspark-1.2, -Pspark-1.3, etc.) as needed. For Hive, check the hive/pom.xml and adjust the version installed as well. The correct Maven
artifacts can be found for every version of MapR at http://doc.mapr.com
MapR 3.x
```
mvn clean package -Pmapr3 -DskipTests
```
MapR 4.0.x
```
mvn clean package -Pmapr40 -DskipTests
```
MapR 4.1
```
mvn clean package -Pmapr41 -DskipTests
```
MapR 5.0
```
mvn clean package -Pmapr50 -DskipTests
```
Yarn (Hadoop 2.7.x)
Available profiles are
```
mvn clean package -Pspark-1.4 -Dspark.version=1.4.1 -Dhadoop.version=2.7.0 -Phadoop-2.6 -Pyarn -DskipTests
```
Yarn (Hadoop 2.6.x)
```
mvn clean package -Pspark-1.1 -Dhadoop.version=2.6.0 -Phadoop-2.6 -Pyarn -DskipTests
```
Yarn (Hadoop 2.4.x)
```
mvn clean package -Pspark-1.1 -Dhadoop.version=2.4.0 -Phadoop-2.4 -Pyarn -DskipTests
```
Yarn (Hadoop 2.3.x)
```
mvn clean package -Pspark-1.1 -Dhadoop.version=2.3.0 -Phadoop-2.3 -Pyarn -DskipTests
```
Yarn (Hadoop 2.2.x)
```
mvn clean package -Pspark-1.1 -Dhadoop.version=2.2.0 -Phadoop-2.2 -Pyarn -DskipTests
-Pmapr3
-Pmapr40
-Pmapr41
-Pmapr50
```
Ignite (1.1.0-incubating and later)
Here're some examples:
```
# basic build
mvn clean package -Pspark-1.5 -Phadoop-2.4 -Pyarn -Ppyspark
# spark-cassandra integration
mvn clean package -Pcassandra-spark-1.5 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
# with CDH
mvn clean package -Pspark-1.2 -Dhadoop.version=2.5.0-cdh5.3.0 -Phadoop-2.4 -Pvendor-repo -DskipTests
# with MapR
mvn clean package -Pspark-1.5 -Pmapr50 -DskipTests
```
#### Ignite Interpreter
```
mvn clean package -Dignite.version=1.1.0-incubating -DskipTests
```
Spark-Cassandra integration (Spark 1.1.x)
```
mvn clean package -Pcassandra-spark-1.1 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
```
Spark-Cassandra integration (Spark 1.2.x)
```
mvn clean package -Pcassandra-spark-1.2 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
```
Spark-Cassandra integration (Spark 1.3.x)
```
mvn clean package -Pcassandra-spark-1.3 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
```
Spark-Cassandra integration (Spark 1.4.x)
```
mvn clean package -Pcassandra-spark-1.4 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
```
Spark-Cassandra integration (Spark 1.5.x)
```
mvn clean package -Pcassandra-spark-1.5 -Dhadoop.version=2.6.0 -Phadoop-2.6 -DskipTests
```
### Configure
If you wish to configure Zeppelin option (like port number), configure the following files:

View file

@ -328,15 +328,6 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>

View file

@ -121,7 +121,10 @@ public abstract class Interpreter {
* Called when interpreter is no longer used.
*/
public void destroy() {
getScheduler().stop();
Scheduler scheduler = getScheduler();
if (scheduler != null) {
scheduler.stop();
}
}

View file

@ -23,29 +23,21 @@ import java.util.*;
/**
* Interpreter result template.
*
* @author Leemoonsoo
*
*/
public class InterpreterResult implements Serializable {
/**
* Type of result after code execution.
*
* @author Leemoonsoo
*
*/
public static enum Code {
SUCCESS,
INCOMPLETE,
ERROR
ERROR,
KEEP_PREVIOUS_RESULT
}
/**
* Type of Data.
*
* @author Leemoonsoo
*
*/
public static enum Type {
TEXT,
@ -99,7 +91,7 @@ public class InterpreterResult implements Serializable {
int magicLength = lastType.getValue().name().length() + 1;
// 1 for the last \n or space after magic
int subStringPos = magicLength + lastType.getKey() + 1;
return msg.substring(subStringPos);
return msg.substring(subStringPos);
}
}
@ -116,7 +108,7 @@ public class InterpreterResult implements Serializable {
return lastType.getValue();
}
}
private int getIndexOfType(String msg, Type t) {
if (msg == null) {
return 0;
@ -124,7 +116,7 @@ public class InterpreterResult implements Serializable {
String typeString = "%" + t.name().toLowerCase();
return StringUtils.indexOf(msg, typeString );
}
private TreeMap<Integer, Type> buildIndexMap(String msg) {
int lastIndexOftypes = 0;
TreeMap<Integer, Type> typesLastIndexInMsg = new TreeMap<Integer, Type>();

View file

@ -329,9 +329,13 @@ public class RemoteInterpreter extends Interpreter {
public Scheduler getScheduler() {
int maxConcurrency = 10;
RemoteInterpreterProcess interpreterProcess = getInterpreterProcess();
return SchedulerFactory.singleton().createOrGetRemoteScheduler(
"remoteinterpreter_" + interpreterProcess.hashCode(), getInterpreterProcess(),
maxConcurrency);
if (interpreterProcess == null) {
return null;
} else {
return SchedulerFactory.singleton().createOrGetRemoteScheduler(
"remoteinterpreter_" + interpreterProcess.hashCode(), getInterpreterProcess(),
maxConcurrency);
}
}

View file

@ -37,7 +37,6 @@ import org.apache.zeppelin.display.AngularObjectRegistryListener;
import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.interpreter.ClassloaderInterpreter;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.Interpreter.FormType;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
@ -62,7 +61,8 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
*
* Entry point for Interpreter process.
* Accepting thrift connections from ZeppelinServer.
*/
public class RemoteInterpreterServer
extends Thread
@ -233,6 +233,11 @@ public class RemoteInterpreterServer
result = new InterpreterResult(Code.ERROR, Job.getStack(job.getException()));
} else {
result = (InterpreterResult) job.getReturn();
// in case of job abort in PENDING status, result can be null
if (result == null) {
result = new InterpreterResult(Code.KEEP_PREVIOUS_RESULT);
}
}
return convert(result,
context.getConfig(),
@ -303,8 +308,16 @@ public class RemoteInterpreterServer
@Override
public void cancel(String className, RemoteInterpreterContext interpreterContext)
throws TException {
logger.info("cancel {} {}", className, interpreterContext.getParagraphId());
Interpreter intp = getInterpreter(className);
intp.cancel(convert(interpreterContext));
String jobId = interpreterContext.getParagraphId();
Job job = intp.getScheduler().removeFromWaitingQueue(jobId);
if (job != null) {
job.setStatus(Status.ABORT);
} else {
intp.cancel(convert(interpreterContext));
}
}
@Override

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.scheduler;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -25,10 +26,7 @@ import java.util.concurrent.ExecutorService;
import org.apache.zeppelin.scheduler.Job.Status;
/**
* TODO(moon) : add description.
*
* @author Leemoonsoo
*
* FIFOScheduler runs submitted job sequentially
*/
public class FIFOScheduler implements Scheduler {
List<Job> queue = new LinkedList<Job>();
@ -83,20 +81,38 @@ public class FIFOScheduler implements Scheduler {
}
}
@Override
public Job removeFromWaitingQueue(String jobId) {
synchronized (queue) {
Iterator<Job> it = queue.iterator();
while (it.hasNext()) {
Job job = it.next();
if (job.getId().equals(jobId)) {
it.remove();
return job;
}
}
}
return null;
}
@Override
public void run() {
synchronized (queue) {
while (terminate == false) {
if (runningJob != null || queue.isEmpty() == true) {
try {
queue.wait(500);
} catch (InterruptedException e) {
synchronized (queue) {
if (runningJob != null || queue.isEmpty() == true) {
try {
queue.wait(500);
} catch (InterruptedException e) {
}
continue;
}
continue;
}
runningJob = queue.remove(0);
runningJob = queue.remove(0);
}
final Scheduler scheduler = this;
this.executor.execute(new Runnable() {

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.scheduler;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -25,10 +26,7 @@ import java.util.concurrent.ExecutorService;
import org.apache.zeppelin.scheduler.Job.Status;
/**
* TODO(moon) : add description.
*
* @author Leemoonsoo
*
* Parallel scheduler runs submitted job concurrently.
*/
public class ParallelScheduler implements Scheduler {
List<Job> queue = new LinkedList<Job>();
@ -63,6 +61,21 @@ public class ParallelScheduler implements Scheduler {
return ret;
}
@Override
public Job removeFromWaitingQueue(String jobId) {
synchronized (queue) {
Iterator<Job> it = queue.iterator();
while (it.hasNext()) {
Job job = it.next();
if (job.getId().equals(jobId)) {
it.remove();
return job;
}
}
}
return null;
}
@Override
public Collection<Job> getJobsRunning() {
List<Job> ret = new LinkedList<Job>();
@ -87,9 +100,9 @@ public class ParallelScheduler implements Scheduler {
@Override
public void run() {
synchronized (queue) {
while (terminate == false) {
while (terminate == false) {
Job job = null;
synchronized (queue) {
if (running.size() >= maxConcurrency || queue.isEmpty() == true) {
try {
queue.wait(500);
@ -98,14 +111,12 @@ public class ParallelScheduler implements Scheduler {
continue;
}
Job job = queue.remove(0);
job = queue.remove(0);
running.add(job);
Scheduler scheduler = this;
executor.execute(new JobRunner(scheduler, job));
}
Scheduler scheduler = this;
executor.execute(new JobRunner(scheduler, job));
}
}

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.scheduler;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -32,7 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* RemoteScheduler runs in ZeppelinServer and proxies Scheduler running on RemoteInterpreter
*/
public class RemoteScheduler implements Scheduler {
Logger logger = LoggerFactory.getLogger(RemoteScheduler.class);
@ -107,6 +108,21 @@ public class RemoteScheduler implements Scheduler {
return ret;
}
@Override
public Job removeFromWaitingQueue(String jobId) {
synchronized (queue) {
Iterator<Job> it = queue.iterator();
while (it.hasNext()) {
Job job = it.next();
if (job.getId().equals(jobId)) {
it.remove();
return job;
}
}
}
return null;
}
@Override
public Collection<Job> getJobsRunning() {
List<Job> ret = new LinkedList<Job>();

View file

@ -20,10 +20,7 @@ package org.apache.zeppelin.scheduler;
import java.util.Collection;
/**
* TODO(moon) : add description.
*
* @author Leemoonsoo
*
* Interface for scheduler
*/
public interface Scheduler extends Runnable {
public String getName();
@ -34,5 +31,7 @@ public interface Scheduler extends Runnable {
public void submit(Job job);
public Job removeFromWaitingQueue(String jobId);
public void stop();
}

View file

@ -88,7 +88,30 @@ public class FIFOSchedulerTest extends TestCase {
assertTrue((500 > (Long)job1.getReturn()));
assertEquals(null, job2.getReturn());
}
public void testRemoveFromWaitingQueue() throws InterruptedException{
Scheduler s = schedulerSvc.createOrGetFIFOScheduler("test");
assertEquals(0, s.getJobsRunning().size());
assertEquals(0, s.getJobsWaiting().size());
Job job1 = new SleepingJob("job1", null, 500);
Job job2 = new SleepingJob("job2", null, 500);
s.submit(job1);
s.submit(job2);
Thread.sleep(200);
job1.abort();
job2.abort();
Thread.sleep(200);
assertEquals(Status.ABORT, job1.getStatus());
assertEquals(Status.ABORT, job2.getStatus());
assertTrue((500 > (Long)job1.getReturn()));
assertEquals(null, job2.getReturn());
}
}

View file

@ -18,6 +18,8 @@
package org.apache.zeppelin.scheduler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
@ -33,6 +35,7 @@ import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreter;
import org.apache.zeppelin.interpreter.remote.mock.MockInterpreterA;
import org.apache.zeppelin.scheduler.Job.Status;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -127,7 +130,7 @@ public class RemoteSchedulerTest {
Thread.sleep(TICK_WAIT);
cycles++;
}
assertTrue(job.isTerminated());
assertEquals(0, scheduler.getJobsWaiting().size());
assertEquals(0, scheduler.getJobsRunning().size());
@ -136,4 +139,133 @@ public class RemoteSchedulerTest {
schedulerSvc.removeScheduler("test");
}
@Test
public void testAbortOnPending() throws Exception {
Properties p = new Properties();
final InterpreterGroup intpGroup = new InterpreterGroup();
Map<String, String> env = new HashMap<String, String>();
env.put("ZEPPELIN_CLASSPATH", new File("./target/test-classes").getAbsolutePath());
final RemoteInterpreter intpA = new RemoteInterpreter(
p,
MockInterpreterA.class.getName(),
new File("../bin/interpreter.sh").getAbsolutePath(),
"fake",
env,
10 * 1000
);
intpGroup.add(intpA);
intpA.setInterpreterGroup(intpGroup);
intpA.open();
Scheduler scheduler = schedulerSvc.createOrGetRemoteScheduler("test",
intpA.getInterpreterProcess(),
10);
Job job1 = new Job("jobId1", "jobName1", null, 200) {
InterpreterContext context = new InterpreterContext(
"note",
"jobId1",
"title",
"text",
new HashMap<String, Object>(),
new GUI(),
new AngularObjectRegistry(intpGroup.getId(), null),
new LinkedList<InterpreterContextRunner>());
@Override
public int progress() {
return 0;
}
@Override
public Map<String, Object> info() {
return null;
}
@Override
protected Object jobRun() throws Throwable {
intpA.interpret("1000", context);
return "1000";
}
@Override
protected boolean jobAbort() {
if (isRunning()) {
intpA.cancel(context);
}
return true;
}
};
Job job2 = new Job("jobId2", "jobName2", null, 200) {
InterpreterContext context = new InterpreterContext(
"note",
"jobId2",
"title",
"text",
new HashMap<String, Object>(),
new GUI(),
new AngularObjectRegistry(intpGroup.getId(), null),
new LinkedList<InterpreterContextRunner>());
@Override
public int progress() {
return 0;
}
@Override
public Map<String, Object> info() {
return null;
}
@Override
protected Object jobRun() throws Throwable {
intpA.interpret("1000", context);
return "1000";
}
@Override
protected boolean jobAbort() {
if (isRunning()) {
intpA.cancel(context);
}
return true;
}
};
job2.setResult("result2");
scheduler.submit(job1);
scheduler.submit(job2);
int cycles = 0;
while (!job1.isRunning() && cycles < MAX_WAIT_CYCLES) {
Thread.sleep(TICK_WAIT);
cycles++;
}
assertTrue(job1.isRunning());
assertTrue(job2.getStatus() == Status.PENDING);
job2.abort();
cycles = 0;
while (!job1.isTerminated() && cycles < MAX_WAIT_CYCLES) {
Thread.sleep(TICK_WAIT);
cycles++;
}
assertNotNull(job1.getDateFinished());
assertTrue(job1.isTerminated());
assertNull(job2.getDateFinished());
assertTrue(job2.isTerminated());
assertEquals("result2", job2.getReturn());
intpA.close();
schedulerSvc.removeScheduler("test");
}
}

View file

@ -55,6 +55,8 @@ public class Message {
CLONE_NOTE, // [c-s] clone new notebook
// @param id id of note to clone
// @param name name fpor the cloned note
IMPORT_NOTE, // [c-s] import notebook
// @param object notebook
NOTE_UPDATE,
RUN_PARAGRAPH, // [c-s] run paragraph

View file

@ -16,20 +16,17 @@
*/
package org.apache.zeppelin.socket;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectRegistryListener;
import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.JobListenerFactory;
@ -119,6 +116,9 @@ public class NotebookServer extends WebSocketServlet implements
case CLONE_NOTE:
cloneNote(conn, notebook, messagereceived);
break;
case IMPORT_NOTE:
importNote(conn, notebook, messagereceived);
break;
case COMMIT_PARAGRAPH:
updateParagraph(conn, notebook, messagereceived);
break;
@ -171,7 +171,7 @@ public class NotebookServer extends WebSocketServlet implements
}
}
private Message deserializeMessage(String msg) {
protected Message deserializeMessage(String msg) {
return gson.fromJson(msg, Message.class);
}
@ -467,6 +467,61 @@ public class NotebookServer extends WebSocketServlet implements
broadcastNoteList();
}
protected Note importNote(NotebookSocket conn, Notebook notebook, Message fromMessage)
throws IOException {
Note note = notebook.createNote();
if (fromMessage != null) {
String noteName = (String) ((Map) fromMessage.get("notebook")).get("name");
if (noteName == null || noteName.isEmpty()) {
noteName = "Note " + note.getId();
}
note.setName(noteName);
ArrayList<Map> paragraphs = ((Map<String, ArrayList>) fromMessage.get("notebook"))
.get("paragraphs");
if (paragraphs.size() > 0) {
for (Map paragraph : paragraphs) {
try {
Paragraph p = note.addParagraph();
String text = (String) paragraph.get("text");
p.setText(text);
p.setTitle((String) paragraph.get("title"));
Map<String, Object> params = (Map<String, Object>) ((Map) paragraph
.get("settings")).get("params");
Map<String, Input> forms = (Map<String, Input>) ((Map) paragraph
.get("settings")).get("forms");
if (params != null) {
p.settings.setParams(params);
}
if (forms != null) {
p.settings.setForms(forms);
}
Map<String, Object> result = (Map) paragraph.get("result");
if (result != null) {
InterpreterResult.Code code = InterpreterResult.Code
.valueOf((String) result.get("code"));
InterpreterResult.Type type = InterpreterResult.Type
.valueOf((String) result.get("type"));
String msg = (String) result.get("msg");
p.setReturn(new InterpreterResult(code, type, msg), null);
}
Map<String, Object> config = (Map<String, Object>) paragraph
.get("config");
p.setConfig(config);
} catch (Exception e) {
LOG.error("Exception while setting parameter in paragraph", e);
}
}
}
}
note.persist();
broadcastNote(note);
broadcastNoteList();
return note;
}
private void removeParagraph(NotebookSocket conn, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");

View file

@ -299,7 +299,7 @@ public class ZeppelinIT {
notebookTitles.add(el.getText());
}
WebElement createNoteLink = driver.findElement(By.xpath("//div[contains(@class, \"col-md-4\")]/div/h5/a"));
WebElement createNoteLink = driver.findElement(By.xpath("//div[contains(@class, \"col-md-4\")]/div/h5/a[contains(.,'Create new note')]"));
createNoteLink.click();
WebDriverWait block = new WebDriverWait(driver, 10);

View file

@ -136,6 +136,27 @@ public class NotebookServerTest extends AbstractTestRestApi {
notebook.removeNote(note1.getId());
}
@Test
public void testImportNotebook() throws IOException {
String msg = "{\"op\":\"IMPORT_NOTE\",\"data\":" +
"{\"notebook\":{\"paragraphs\": [{\"text\": \"Test " +
"paragraphs import\",\"config\":{},\"settings\":{}}]," +
"\"name\": \"Test Zeppelin notebook import\",\"config\": " +
"{}}}}";
Message messageReceived = notebookServer.deserializeMessage(msg);
Note note = null;
try {
note = notebookServer.importNote(null, notebook, messageReceived);
} catch (NullPointerException e) {
//broadcastNoteList(); failed nothing to worry.
}
assertNotEquals(null, notebook.getNote(note.getId()));
assertEquals("Test Zeppelin notebook import", notebook.getNote(note.getId()).getName());
assertEquals("Test paragraphs import", notebook.getNote(note.getId()).getParagraphs().get(0).getText());
notebook.removeNote(note.getId());
}
private NotebookSocket createWebSocket() {
NotebookSocket sock = mock(NotebookSocket.class);
when(sock.getRequest()).thenReturn(createHttpServletRequest());

View file

@ -130,30 +130,37 @@ a.navbar-brand:hover {
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
color: #D3D3D3;
}
.navbar-nav .open .dropdown-menu > .scrollbar-container > li > a {
padding: 5px 15px 5px 25px;
line-height: 20px;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a {
color: #D3D3D3;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > li > a:focus {
color: #fff;
background-color: transparent;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a,
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .scrollbar-container > .active > a:focus {
color: #fff;
background-color: #080808;
}
.server-status{
.server-status {
float: right;
}
.navbar-inverse .navbar-nav .open .dropdown-menu .divider {
background-color: #3071A9;
}
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
border-color: #3071A9;
}
@ -210,7 +217,7 @@ a.navbar-brand:hover {
.box-heading {
position: relative;
max-width: 100%;
font-weight:300;
font-weight: 300;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: middle;
@ -310,3 +317,374 @@ This part should be removed when new version of bootstrap handles this issue.
.btn-group > .popover + .btn {
margin-left: -1px;
}
.display-inline {
display: inline;
float: left;
}
#noteImportModal .modal-body {
min-height: 420px;
overflow: hidden;
}
#noteImportModal .modal-footer {
min-height: 65px;
}
#noteImportModal .display-inline a {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 5px;
color: #999;
cursor: pointer;
display: block;
float: left;
font-size: 98px;
height: 240px;
margin: 0 10px 16px;
padding-top: 60px;
text-align: center;
text-decoration: none;
width: 264px;
}
#noteImportModal .display-inline a:hover {
background-color: #eee;
}
#noteImportModal .display-inline a p {
font-size: 14px;
}
/* ------------------------------------------- */
/* Slide Top
/* ------------------------------------------- */
.slide-top {
-webkit-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
-webkit-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
}
.slide-top.ng-enter {
transform: translateY(60px);
-ms-transform: translateY(60px);
-webkit-transform: translateY(60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 0;
}
.slide-top.ng-enter-active {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
opacity: 1;
}
.slide-top.ng-leave {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-top.ng-leave-active {
transform: translateY(60px);
-ms-transform: translateY(60px);
-webkit-transform: translateY(60px);
opacity: 0;
}
.slide-top.ng-hide-add {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-top.ng-hide-add.ng-hide-add-active {
transform: translateY(60px);
-ms-transform: translateY(60px);
-webkit-transform: translateY(60px);
opacity: 0;
}
.slide-top.ng-hide-remove {
transform: translateY(60px);
-ms-transform: translateY(60px);
-webkit-transform: translateY(60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
display: block !important;
opacity: 0;
}
.slide-top.ng-hide-remove.ng-hide-remove-active {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
opacity: 1;
}
/* ------------------------------------------- */
/* Slide Right
/* ------------------------------------------- */
.slide-right {
-webkit-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
-webkit-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
}
.slide-right.ng-enter {
transform: translateX(60px);
-ms-transform: translateX(60px);
-webkit-transform: translateX(60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 0;
}
.slide-right.ng-enter-active {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
opacity: 1;
}
.slide-right.ng-leave {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-right.ng-leave-active {
transform: translateX(60px);
-ms-transform: translateX(60px);
-webkit-transform: translateX(60px);
opacity: 0;
}
.slide-right.ng-hide-add {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-right.ng-hide-add.ng-hide-add-active {
transform: translateX(60px);
-ms-transform: translateX(60px);
-webkit-transform: translateX(60px);
opacity: 0;
}
.slide-right.ng-hide-remove {
transform: translateX(60px);
-ms-transform: translateX(60px);
-webkit-transform: translateX(60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
display: block !important;
opacity: 0;
}
.slide-right.ng-hide-remove.ng-hide-remove-active {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
opacity: 1;
}
/* ------------------------------------------- */
/* Slide Left
/* ------------------------------------------- */
.slide-left {
-webkit-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
-webkit-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
}
.slide-left.ng-enter {
transform: translateX(-60px);
-ms-transform: translateX(-60px);
-webkit-transform: translateX(-60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 0;
}
.slide-left.ng-enter-active {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
opacity: 1;
}
.slide-left.ng-leave {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-left.ng-leave-active {
transform: translateX(-60px);
-ms-transform: translateX(-60px);
-webkit-transform: translateX(-60px);
opacity: 0;
}
.slide-left.ng-hide-add {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-left.ng-hide-add.ng-hide-add-active {
transform: translateX(-60px);
-ms-transform: translateX(-60px);
-webkit-transform: translateX(-60px);
opacity: 0;
}
.slide-left.ng-hide-remove {
transform: translateX(-60px);
-ms-transform: translateX(-60px);
-webkit-transform: translateX(-60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
display: block !important;
opacity: 0;
}
.slide-left.ng-hide-remove.ng-hide-remove-active {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
opacity: 1;
}
/* ------------------------------------------- */
/* Slide Down
/* ------------------------------------------- */
.slide-down {
-webkit-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition: all 0 cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
-webkit-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-moz-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-ms-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
-o-transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* easeOutQuad */
}
.slide-down.ng-enter {
transform: translateY(-60px);
-ms-transform: translateY(-60px);
-webkit-transform: translateY(-60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 0;
}
.slide-down.ng-enter-active {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
opacity: 1;
}
.slide-down.ng-leave {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-down.ng-leave-active {
transform: translateY(-60px);
-ms-transform: translateY(-60px);
-webkit-transform: translateY(-60px);
opacity: 0;
}
.slide-down.ng-hide-add {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
opacity: 1;
}
.slide-down.ng-hide-add.ng-hide-add-active {
transform: translateY(-60px);
-ms-transform: translateY(-60px);
-webkit-transform: translateY(-60px);
opacity: 0;
}
.slide-down.ng-hide-remove {
transform: translateY(-60px);
-ms-transform: translateY(-60px);
-webkit-transform: translateY(-60px);
transition-duration: 250ms;
-webkit-transition-duration: 250ms;
display: block !important;
opacity: 0;
}
.slide-down.ng-hide-remove.ng-hide-remove-active {
transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
opacity: 1;
}

View file

@ -29,6 +29,8 @@ limitations under the License.
<h4>Notebook</h4>
<div>
<h5><a href="" data-toggle="modal" data-target="#noteImportModal" style="text-decoration: none;">
<i style="font-size: 15px;" class="fa fa-upload"></i> Import note</a></h5>
<h5><a href="" data-toggle="modal" data-target="#noteNameModal" style="text-decoration: none;">
<i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
<ul style="list-style-type: none;">

View file

@ -14,41 +14,45 @@
'use strict';
angular.module('zeppelinWebApp').controller('NotenameCtrl', function($scope, $rootScope, $routeParams, websocketMsgSrv) {
angular.module('zeppelinWebApp').controller('NotenameCtrl', function($scope, $rootScope, $routeParams, websocketMsgSrv, $location) {
var vm = this;
vm.websocketMsgSrv = websocketMsgSrv;
$scope.note = {};
vm.createNote = function(){
if(!vm.clone){
vm.websocketMsgSrv.createNotebook($scope.note.notename);
}else{
var noteId = $routeParams.noteId;
vm.websocketMsgSrv.cloneNotebook(noteId, $scope.note.notename);
}
vm.createNote = function() {
if (!vm.clone) {
vm.websocketMsgSrv.createNotebook($scope.note.notename);
} else {
var noteId = $routeParams.noteId;
vm.websocketMsgSrv.cloneNotebook(noteId, $scope.note.notename);
}
};
$scope.$on('setNoteContent', function(event, note) {
if(note !== undefined) {
window.location = '#/notebook/' + note.id;
console.log(note);
//a hack, to make it run only after notebook creation
//it should not run i.e in case of linking to the paragraph
if (note && $location.path().indexOf(note.id) < 0) {
$location.path('notebook/' + note.id);
}
});
vm.preVisible = function(clone){
var generatedName = vm.generateName();
$scope.note.notename = 'Note ' + generatedName;
vm.clone = clone;
$scope.$apply();
vm.preVisible = function(clone) {
var generatedName = vm.generateName();
$scope.note.notename = 'Note ' + generatedName;
vm.clone = clone;
$scope.$apply();
};
vm.generateName = function () {
var DICTIONARY = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ];
var randIndex, name = '';
for (var i = 0; i < 9; i++) {
randIndex = Math.floor(Math.random() * 32);
name += DICTIONARY[randIndex];
}
return name;
};
var DICTIONARY = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ];
var randIndex, name = '';
for (var i = 0; i < 9; i++) {
randIndex = Math.floor(Math.random() * 32);
name += DICTIONARY[randIndex];
}
return name;
};
});

View file

@ -0,0 +1,76 @@
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div id="noteImportModal" class="modal fade" role="dialog"
tabindex='-1'>
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content" id="NoteImportCtrl" ng-init="NoteImportInit">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Import new note</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="noteImportName">Import AS</label>
<input placeholder="Note name" type="text" class="form-control" id="noteImportName"
ng-model="note.noteImportName">
</div>
<div class="form-group" ng-show="note.errorText">
<div class="alert alert-danger">{{note.errorText}}</div>
</div>
<div class="form-group slide-left" ng-show="note.step1">
<div class="display-inline">
<a class="fa fa-cloud-upload import-file-upload" ng-click="uploadFile()">
<p>Choose a JSON here</p>
</a>
</div>
<div style="display: none">
<input placeholder="Note name" type="file" class="form-control" id="noteImportFile"
ng-model="note.importFile" onchange="angular.element(this).scope().importFile(this)">
</div>
<div class="display-inline">
<a href="javascript:void(0);" ng-click="uploadURL()" class="fa fa-link">
<p>Add from URL</p>
</a>
</div>
</div>
<div class="form-group slide-right" ng-show="note.step2">
<label for="noteImportUrl">URL</label>
<input placeholder="Note name" type="text" class="form-control" id="noteImportUrl"
ng-model="note.importUrl">
</div>
</div>
<div class="modal-footer">
<div ng-show="note.step2">
<button type="button" id="importBackButton"
class="btn btn-default"
ng-click="noteimportctrl.importBack()">Back
</button>
<button type="button" id="importNoteButton"
class="btn btn-default"
ng-click="noteimportctrl.importNote()">Import Note
</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,110 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
angular.module('zeppelinWebApp').controller('NoteImportCtrl', function($scope, $timeout, websocketMsgSrv) {
var vm = this;
$scope.note = {};
$scope.note.step1 = true;
$scope.note.step2 = false;
vm.resetFlags = function() {
$scope.note = {};
$scope.note.step1 = true;
$scope.note.step2 = false;
angular.element('#noteImportFile').val('');
};
$scope.uploadFile = function() {
angular.element('#noteImportFile').click();
};
$scope.importFile = function(element) {
$scope.note.errorText = '';
$scope.note.importFile = element.files[0];
var file = $scope.note.importFile;
var reader = new FileReader();
reader.onloadend = function() {
vm.processImportJson(reader.result);
};
if (file) {
reader.readAsText(file);
}
};
$scope.uploadURL = function() {
$scope.note.errorText = '';
$scope.note.step1 = false;
$timeout(function() {
$scope.note.step2 = true;
}, 400);
};
vm.importBack = function() {
$scope.note.errorText = '';
$timeout(function() {
$scope.note.step1 = true;
}, 400);
$scope.note.step2 = false;
};
vm.importNote = function() {
$scope.note.errorText = '';
if ($scope.note.importUrl) {
jQuery.getJSON($scope.note.importUrl, function(result) {
vm.processImportJson(result);
}).fail(function() {
$scope.note.errorText = 'Unable to Fetch URL';
$scope.$apply();
});
}
else {
$scope.note.errorText = 'Enter URL';
$scope.$apply();
}
};
vm.processImportJson = function(result) {
if (typeof result !== 'object') {
try {
result = JSON.parse(result);
} catch (e) {
$scope.note.errorText = 'JSON parse exception';
$scope.$apply();
return;
}
}
if (result.paragraphs && result.paragraphs.length > 0) {
if (!$scope.note.noteImportName) {
$scope.note.noteImportName = result.name;
} else {
result.name = $scope.note.noteImportName;
}
websocketMsgSrv.importNotebook(result);
//angular.element('#noteImportModal').modal('hide');
} else {
$scope.note.errorText = 'Invalid JSON';
}
$scope.$apply();
};
$scope.$on('setNoteMenu', function(event, notes) {
vm.resetFlags();
angular.element('#noteImportModal').modal('hide');
});
});

View file

@ -113,6 +113,15 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope,
});
},
importNotebook: function(notebook) {
websocketEvents.sendNewEvent({
op: 'IMPORT_NOTE',
data: {
notebook: notebook
}
});
},
isConnected: function(){
return websocketEvents.isConnected();
}

View file

@ -73,6 +73,9 @@ limitations under the License.
<div ng-controller="NotenameCtrl as notenamectrl">
<div id="note-modal-container" ng-include src="'components/noteName-create/note-name-dialog.html'"></div>
</div>
<div ng-controller="NoteImportCtrl as noteimportctrl">
<div id="note-import-container" ng-include src="'components/noteName-import/note-import-dialog.html'"></div>
</div>
<!-- build:js(.) scripts/oldieshim.js -->
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
@ -129,6 +132,7 @@ limitations under the License.
<script src="components/navbar/navbar.controller.js"></script>
<script src="components/ngescape/ngescape.directive.js"></script>
<script src="components/noteName-create/notename.controller.js"></script>
<script src="components/noteName-import/notenameImport.controller.js"></script>
<script src="components/popover-html-unsafe/popover-html-unsafe.directive.js"></script>
<script src="components/ngenter/ngenter.directive.js"></script>
<script src="components/dropdowninput/dropdowninput.directive.js"></script>

View file

@ -22,6 +22,8 @@ import org.apache.zeppelin.display.GUI;
import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.interpreter.*;
import org.apache.zeppelin.interpreter.Interpreter.FormType;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.JobListener;
import org.slf4j.Logger;
@ -205,13 +207,22 @@ public class Paragraph extends Job implements Serializable, Cloneable {
}
logger().debug("RUN : " + script);
InterpreterResult ret = repl.interpret(script, getInterpreterContext());
if (Code.KEEP_PREVIOUS_RESULT == ret.code()) {
return getReturn();
}
return ret;
}
@Override
protected boolean jobAbort() {
Interpreter repl = getRepl(getRequiredReplName());
repl.cancel(getInterpreterContext());
Job job = repl.getScheduler().removeFromWaitingQueue(getId());
if (job != null) {
job.setStatus(Status.ABORT);
} else {
repl.cancel(getInterpreterContext());
}
return true;
}