Merge remote-tracking branch 'upstream/master' into scalding

This commit is contained in:
Sriram Krishnan 2015-12-21 14:08:59 -08:00
commit 8944b0c3ae
21 changed files with 614 additions and 124 deletions

View file

@ -70,6 +70,15 @@ function addEachJarInDir(){
fi
}
function addEachJarInDirRecursive(){
if [[ -d "${1}" ]]; then
for jar in $(find -L "${1}" -type f -name '*jar'); do
ZEPPELIN_CLASSPATH="$jar:$ZEPPELIN_CLASSPATH"
done
fi
}
function addJarInDir(){
if [[ -d "${1}" ]]; then
ZEPPELIN_CLASSPATH="${1}/*:${ZEPPELIN_CLASSPATH}"

View file

@ -87,7 +87,7 @@ if [[ "${INTERPRETER_ID}" == "spark" ]]; then
# add Hadoop jars into classpath
if [[ -n "${HADOOP_HOME}" ]]; then
# Apache
addEachJarInDir "${HADOOP_HOME}/share"
addEachJarInDirRecursive "${HADOOP_HOME}/share"
# CDH
addJarInDir "${HADOOP_HOME}"

View file

@ -361,3 +361,37 @@ limitations under the License.
</td>
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
<th>Restart an interpreter</th>
<th></th>
</tr>
<tr>
<td>Description</td>
<td>This ```PUT``` method restart the given interpreter id.</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/interpreter/setting/restart/[interpreter ID]```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td> Fail code</td>
<td> 500 </td>
</tr>
<tr>
<td> sample JSON response
</td>
<td>
<pre>{"status":"OK"}</pre>
</td>
</tr>
</table>

View file

@ -20,12 +20,12 @@ limitations under the License.
{% include JB/setup %}
## Zeppelin REST API
Zeppelin provides several REST API's for interaction and remote activation of zeppelin functionality.
Zeppelin provides several REST APIs for interaction and remote activation of zeppelin functionality.
All REST API are available starting with the following endpoint ```http://[zeppelin-server]:[zeppelin-port]/api```
All REST APIs are available starting with the following endpoint ```http://[zeppelin-server]:[zeppelin-port]/api```
Note that zeppein REST API receive or return JSON objects, it it recommended you install some JSON view such as
[JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc)
Note that zeppelin REST APIs receive or return JSON objects, it is recommended for you to install some JSON viewer
such as [JSONView](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc)
If you work with zeppelin and find a need for an additional REST API please [file an issue or send us mail](../../community.html)
@ -33,7 +33,7 @@ limitations under the License.
<br />
### Notebook REST API list
Notebooks REST API supports the following operations: List, Create, Delete & Clone as detailed in the following table
Notebooks REST API supports the following operations: List, Create, Get, Delete, Clone, Run as detailed in the following table
<table class="table-configuration">
<col width="200">
@ -43,7 +43,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method list the available notebooks on your server.
<td>This ```GET``` method lists the available notebooks on your server.
Notebook JSON contains the ```name``` and ```id``` of all notebooks.
</td>
</tr>
@ -75,8 +75,8 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method create a new notebook using the given name or default name if none given.
The body field of the returned JSON contain the new notebook id.
<td>This ```POST``` method creates a new notebook using the given name or default name if none given.
The body field of the returned JSON contains the new notebook id.
</td>
</tr>
<tr>
@ -119,6 +119,108 @@ limitations under the License.
</tr>
</table>
<br/>
<table class="table-configuration">
<col width="200">
<tr>
<th>Get notebook</th>
<th></th>
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method retrieves an existing notebook's information using the given id.
The body field of the returned JSON contain information about paragraphs in the notebook.
</td>
</tr>
<tr>
<td>URL</td>
<td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/[notebookId]```</td>
</tr>
<tr>
<td>Success code</td>
<td>200</td>
</tr>
<tr>
<td> Fail code</td>
<td> 500 </td>
</tr>
<tr>
<td> sample JSON response </td>
<td><pre>
{
"status": "OK",
"message": "",
"body": {
"paragraphs": [
{
"text": "%sql \nselect age, count(1) value\nfrom bank \nwhere age < 30 \ngroup by age \norder by age",
"config": {
"colWidth": 4,
"graph": {
"mode": "multiBarChart",
"height": 300,
"optionOpen": false,
"keys": [
{
"name": "age",
"index": 0,
"aggr": "sum"
}
],
"values": [
{
"name": "value",
"index": 1,
"aggr": "sum"
}
],
"groups": [],
"scatter": {
"xAxis": {
"name": "age",
"index": 0,
"aggr": "sum"
},
"yAxis": {
"name": "value",
"index": 1,
"aggr": "sum"
}
}
}
},
"settings": {
"params": {},
"forms": {}
},
"jobName": "paragraph_1423500782552_-1439281894",
"id": "20150210-015302_1492795503",
"result": {
"code": "SUCCESS",
"type": "TABLE",
"msg": "age\tvalue\n19\t4\n20\t3\n21\t7\n22\t9\n23\t20\n24\t24\n25\t44\n26\t77\n27\t94\n28\t103\n29\t97\n"
},
"dateCreated": "Feb 10, 2015 1:53:02 AM",
"dateStarted": "Jul 3, 2015 1:43:17 PM",
"dateFinished": "Jul 3, 2015 1:43:23 PM",
"status": "FINISHED",
"progressUpdateIntervalMs": 500
}
],
"name": "Zeppelin Tutorial",
"id": "2A94M5J1Z",
"angularObjects": {},
"config": {
"looknfeel": "default"
},
"info": {}
}
}
</pre></td>
</tr>
</table>
<br/>
<table class="table-configuration">
@ -129,7 +231,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method delete a notebook by the given notebook id.
<td>This ```DELETE``` method deletes a notebook by the given notebook id.
</td>
</tr>
<tr>
@ -160,9 +262,9 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method clone a notebook by the given id and create a new notebook using the given name
<td>This ```POST``` method clones a notebook by the given id and create a new notebook using the given name
or default name if none given.
The body field of the returned JSON contain the new notebook id.
The body field of the returned JSON contains the new notebook id.
</td>
</tr>
<tr>
@ -197,7 +299,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method run all paragraph in the given notebook id.
<td>This ```POST``` method runs all paragraph in the given notebook id.
</td>
</tr>
<tr>
@ -228,7 +330,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method stop all paragraph in the given notebook id.
<td>This ```DELETE``` method stops all paragraph in the given notebook id.
</td>
</tr>
<tr>
@ -261,7 +363,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method get all paragraph status by the given notebook id.
<td>This ```GET``` method gets all paragraph status by the given notebook id.
The body field of the returned JSON contains of the array that compose of the paragraph id, paragraph status, paragraph finish date, paragraph started date.
</td>
</tr>
@ -293,7 +395,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method run the paragraph by given notebook and paragraph id.
<td>This ```POST``` method runs the paragraph by given notebook and paragraph id.
</td>
</tr>
<tr>
@ -308,6 +410,18 @@ limitations under the License.
<td> Fail code</td>
<td> 500 </td>
</tr>
<tr>
<td> sample JSON input (optional, only needed when if you want to update dynamic form's value) </td>
<td><pre>
{
"name": "name of new notebook",
"params": {
"formLabel1": "value1",
"formLabel2": "value2"
}
}
</pre></td>
</tr>
<tr>
<td> sample JSON response </td>
<td><pre>{"status":"OK"}</pre></td>
@ -324,7 +438,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method stop the paragraph by given notebook and paragraph id.
<td>This ```DELETE``` method stops the paragraph by given notebook and paragraph id.
</td>
</tr>
<tr>
@ -355,7 +469,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```POST``` method add cron job by the given notebook id.
<td>This ```POST``` method adds cron job by the given notebook id.
</td>
</tr>
<tr>
@ -390,7 +504,7 @@ limitations under the License.
</tr>
<tr>
<td>Description</td>
<td>This ```DELETE``` method remove cron job by the given notebook id.
<td>This ```DELETE``` method removes cron job by the given notebook id.
</td>
</tr>
<tr>
@ -416,13 +530,13 @@ limitations under the License.
<table class="table-configuration">
<col width="200">
<tr>
<th>Get clone job</th>
<th>Get cron job</th>
<th></th>
</tr>
<tr>
<td>Description</td>
<td>This ```GET``` method get cron job expression of given notebook id.
The body field of the returned JSON contain the cron expression.
<td>This ```GET``` method gets cron job expression of given notebook id.
The body field of the returned JSON contains the cron expression.
</td>
</tr>
<tr>

View file

@ -58,6 +58,11 @@ import org.apache.zeppelin.spark.dep.DependencyContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import py4j.GatewayServer;
/**
@ -368,12 +373,96 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
return sparkInterpreter.getProgress(context);
}
@Override
public List<String> completion(String buf, int cursor) {
// not supported
return new LinkedList<String>();
if (buf.length() < cursor) {
cursor = buf.length();
}
String completionString = getCompletionTargetString(buf, cursor);
String completionCommand = "completion.getCompletion('" + completionString + "')";
//start code for completion
SparkInterpreter sparkInterpreter = getSparkInterpreter();
if (sparkInterpreter.getSparkVersion().isUnsupportedVersion() == false
&& pythonscriptRunning == false) {
return new LinkedList<String>();
}
outputStream.reset();
pythonInterpretRequest = new PythonInterpretRequest(completionCommand, "");
statementOutput = null;
synchronized (statementSetNotifier) {
statementSetNotifier.notify();
}
synchronized (statementFinishedNotifier) {
while (statementOutput == null) {
try {
statementFinishedNotifier.wait(1000);
} catch (InterruptedException e) {
// not working
logger.info("wait drop");
return new LinkedList<String>();
}
}
}
if (statementError) {
return new LinkedList<String>();
}
InterpreterResult completionResult = new InterpreterResult(Code.SUCCESS, statementOutput);
//end code for completion
Gson gson = new Gson();
return gson.fromJson(completionResult.message(), LinkedList.class);
}
private String getCompletionTargetString(String text, int cursor) {
String[] completionSeqCharaters = {" ", "\n", "\t"};
int completionEndPosition = cursor;
int completionStartPosition = cursor;
int indexOfReverseSeqPostion = cursor;
String resultCompletionText = "";
String completionScriptText = "";
try {
completionScriptText = text.substring(0, cursor);
}
catch (Exception e) {
logger.error(e.toString());
return null;
}
completionEndPosition = completionScriptText.length();
String tempReverseCompletionText = new StringBuilder(completionScriptText).reverse().toString();
for (String seqCharacter : completionSeqCharaters) {
indexOfReverseSeqPostion = tempReverseCompletionText.indexOf(seqCharacter);
if (indexOfReverseSeqPostion < completionStartPosition && indexOfReverseSeqPostion > 0) {
completionStartPosition = indexOfReverseSeqPostion;
}
}
if (completionStartPosition == completionEndPosition) {
completionStartPosition = 0;
}
else
{
completionStartPosition = completionEndPosition - completionStartPosition;
}
resultCompletionText = completionScriptText.substring(
completionStartPosition , completionEndPosition);
return resultCompletionText;
}
private SparkInterpreter getSparkInterpreter() {
InterpreterGroup intpGroup = getInterpreterGroup();
LazyOpenInterpreter lazy = null;

View file

@ -15,7 +15,7 @@
# limitations under the License.
#
import sys, getopt, traceback
import sys, getopt, traceback, json, re
from py4j.java_gateway import java_import, JavaGateway, GatewayClient
from py4j.protocol import Py4JJavaError
@ -107,6 +107,50 @@ class SparkVersion(object):
def isImportAllPackageUnderSparkSql(self):
return self.version >= self.SPARK_1_3_0
class PySparkCompletion:
def getGlobalCompletion(self):
objectDefList = []
try:
for completionItem in list(globals().iterkeys()):
objectDefList.append(completionItem)
except:
return None
else:
return objectDefList
def getMethodCompletion(self, text_value):
objectDefList = []
completion_target = text_value
try:
if len(completion_target) <= 0:
return None
if text_value[-1] == ".":
completion_target = text_value[:-1]
exec("%s = %s(%s)" % ("objectDefList", "dir", completion_target))
except:
return None
else:
return objectDefList
def getCompletion(self, text_value):
completionList = set()
globalCompletionList = self.getGlobalCompletion()
if globalCompletionList != None:
for completionItem in list(globalCompletionList):
completionList.add(completionItem)
if text_value != None:
objectCompletionList = self.getMethodCompletion(text_value)
if objectCompletionList != None:
for completionItem in list(objectCompletionList):
completionList.add(completionItem)
if len(completionList) <= 0:
print ""
else:
print json.dumps(filter(lambda x : not re.match("^__.*", x), list(completionList)))
output = Logger()
sys.stdout = output
@ -149,6 +193,7 @@ sc = SparkContext(jsc=jsc, gateway=gateway, conf=conf)
sqlc = SQLContext(sc, intp.getSQLContext())
sqlContext = sqlc
completion = PySparkCompletion()
z = PyZeppelinContext(intp.getZeppelinContext())
while True :

View file

@ -26,6 +26,8 @@ import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;
import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
@ -122,6 +124,17 @@ public class NotebookRestApi {
return new JsonResponse(Status.OK, "", notesInfo ).build();
}
@GET
@Path("{notebookId}")
public Response getNotebook(@PathParam("notebookId") String notebookId) throws IOException {
Note note = notebook.getNote(notebookId);
if (note == null) {
return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
}
return new JsonResponse(Status.OK, "", note).build();
}
/**
* Create new note REST API
* @param message - JSON with new note name
@ -260,26 +273,41 @@ public class NotebookRestApi {
/**
* Run paragraph job REST API
* @param
* @param message - JSON with params if user wants to update dynamic form's value
* null, empty string, empty json if user doesn't want to update
* @return JSON with status.OK
* @throws IOException, IllegalArgumentException
*/
@POST
@Path("job/{notebookId}/{paragraphId}")
public Response runParagraph(@PathParam("notebookId") String notebookId,
@PathParam("paragraphId") String paragraphId) throws
@PathParam("paragraphId") String paragraphId,
String message) throws
IOException, IllegalArgumentException {
logger.info("run paragraph job {} {} ", notebookId, paragraphId);
logger.info("run paragraph job {} {} {}", notebookId, paragraphId, message);
Note note = notebook.getNote(notebookId);
if (note == null) {
return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
}
if (note.getParagraph(paragraphId) == null) {
Paragraph paragraph = note.getParagraph(paragraphId);
if (paragraph == null) {
return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build();
}
note.run(paragraphId);
// handle params if presented
if (!StringUtils.isEmpty(message)) {
RunParagraphWithParametersRequest request = gson.fromJson(message,
RunParagraphWithParametersRequest.class);
Map<String, Object> paramsForUpdating = request.getParams();
if (paramsForUpdating != null) {
paragraph.settings.getParams().putAll(paramsForUpdating);
note.persist();
}
}
note.run(paragraph.getId());
return new JsonResponse(Status.OK).build();
}

View file

@ -0,0 +1,35 @@
/*
* 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.Map;
/**
* RunParagraphWithParametersRequest rest api request message
*/
public class RunParagraphWithParametersRequest {
Map<String, Object> params;
public RunParagraphWithParametersRequest() {
}
public Map<String, Object> getParams() {
return params;
}
}

View file

@ -17,37 +17,58 @@
package org.apache.zeppelin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.*;
import org.openqa.selenium.By;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.google.common.base.Function;
/**
* Test Zeppelin with web brower.
* Test Zeppelin with web browser.
*
* To test, ZeppelinServer should be running on port 8080
* On OSX, you'll need firefox 42.0 installed.
* On OSX, you'll need firefox 42.0 installed, then you can run with
*
* PATH=~/Applications/Firefox.app/Contents/MacOS/:$PATH CI="" \
* mvn -Dtest=org.apache.zeppelin.ZeppelinIT -Denforcer.skip=true \
* test -pl zeppelin-server
*
*/
public class ZeppelinIT {
private static final Logger LOG = LoggerFactory.getLogger(ZeppelinIT.class);
private static final long MAX_BROWSER_TIMEOUT_SEC = 30;
private static final long MAX_PARAGRAPH_TIMEOUT_SEC = 60;
private WebDriver driver;
private WebDriver getWebDriver() {
WebDriver driver = null;
private void setWebDriver() {
if (driver == null) {
try {
@ -59,6 +80,7 @@ public class ZeppelinIT {
FirefoxProfile profile = new FirefoxProfile();
driver = new FirefoxDriver(ffox, profile);
} catch (Exception e) {
LOG.error("Starting Firefox failed",e);
}
}
@ -66,6 +88,7 @@ public class ZeppelinIT {
try {
driver = new ChromeDriver();
} catch (Exception e) {
LOG.error("Starting Chrome failed",e);
}
}
@ -73,6 +96,7 @@ public class ZeppelinIT {
try {
driver = new SafariDriver();
} catch (Exception e) {
LOG.error("Starting Safari failed",e);
}
}
@ -88,16 +112,9 @@ public class ZeppelinIT {
driver.get(url);
while (System.currentTimeMillis() - start < 60 * 1000) {
// wait for page load
try {
(new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d) {
return d.findElement(By.partialLinkText("Create new note"))
.isDisplayed();
}
});
loaded = true;
try { // wait for page load
WebElement element = pollingWait(By.partialLinkText("Create new note"), MAX_BROWSER_TIMEOUT_SEC);
loaded = element.isDisplayed();
break;
} catch (TimeoutException e) {
driver.navigate().to(url);
@ -107,8 +124,6 @@ public class ZeppelinIT {
if (loaded == false) {
fail();
}
return driver;
}
@Before
@ -116,8 +131,7 @@ public class ZeppelinIT {
if (!endToEndTestEnabled()) {
return;
}
driver = getWebDriver();
setWebDriver();
}
@After
@ -133,41 +147,45 @@ public class ZeppelinIT {
return "//div[@ng-controller=\"ParagraphCtrl\"][" + paragraphNo +"]";
}
void waitForParagraph(final int paragraphNo, final String state) {
(new WebDriverWait(driver, 60)).until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver d) {
return driver.findElement(By.xpath(getParagraphXPath(paragraphNo)
+ "//div[contains(@class, 'control')]//span[1][contains(.,'" + state + "')]"))
.isDisplayed();
}
;
});
boolean waitForParagraph(final int paragraphNo, final String state) {
By locator = By.xpath(getParagraphXPath(paragraphNo)
+ "//div[contains(@class, 'control')]//span[1][contains(.,'" + state + "')]");
WebElement element = pollingWait(locator, MAX_PARAGRAPH_TIMEOUT_SEC);
return element.isDisplayed();
}
boolean endToEndTestEnabled() {
return null != System.getenv("CI");
}
boolean waitForText(final String txt, final By by) {
boolean waitForText(final String txt, final By locator) {
try {
new WebDriverWait(driver, 5).until(new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d) {
return txt.equals(driver.findElement(by).getText());
}
});
return true;
WebElement element = pollingWait(locator, MAX_BROWSER_TIMEOUT_SEC);
return txt.equals(element.getText());
} catch (TimeoutException e) {
return false;
}
}
public WebElement pollingWait(final By locator, final long timeWait) {
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(timeWait, TimeUnit.SECONDS)
.pollingEvery(1, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);
return wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(locator);
}
});
};
boolean endToEndTestEnabled() {
return null != System.getenv("CI");
}
@Test
public void testAngularDisplay() throws InterruptedException{
if (!endToEndTestEnabled()) {
return;
}
try {
createNewNote();
// wait for first paragraph's " READY " status text
@ -287,6 +305,10 @@ public class ZeppelinIT {
By.xpath(getParagraphXPath(1) + "//div[@id=\"angularTestButton\"]"));
System.out.println("testCreateNotebook Test executed");
} catch (ElementNotVisibleException e) {
File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
}
}
private void createNewNote() {
@ -300,7 +322,7 @@ public class ZeppelinIT {
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);
WebDriverWait block = new WebDriverWait(driver, MAX_BROWSER_TIMEOUT_SEC);
WebElement modal = block.until(ExpectedConditions.visibilityOfElementLocated(By.id("noteNameModal")));
WebElement createNoteButton = modal.findElement(By.id("createNoteButton"));
createNoteButton.click();

View file

@ -34,6 +34,7 @@ import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.rest.message.NewParagraphRequest;
import org.apache.zeppelin.scheduler.Job.Status;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.server.ZeppelinServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@ -193,6 +194,39 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
ZeppelinServer.notebook.removeNote(note.getId());
}
@Test
public void testGetNotebookInfo() throws IOException {
LOG.info("testGetNotebookInfo");
// Create note to get info
Note note = ZeppelinServer.notebook.createNote();
assertNotNull("can't create new note", note);
note.setName("note");
Paragraph paragraph = note.addParagraph();
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
String paragraphText = "%md This is my new paragraph in my new note";
paragraph.setText(paragraphText);
note.persist();
String sourceNoteID = note.getId();
GetMethod get = httpGet("/notebook/" + sourceNoteID);
LOG.info("testGetNotebookInfo \n" + get.getResponseBodyAsString());
assertThat("test notebook get method:", get, isAllowed());
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
assertNotNull(resp);
assertEquals("OK", resp.get("status"));
Map<String, Object> body = (Map<String, Object>) resp.get("body");
List<Map<String, Object>> paragraphs = (List<Map<String, Object>>) body.get("paragraphs");
assertTrue(paragraphs.size() > 0);
assertEquals(paragraphText, paragraphs.get(0).get("text"));
}
@Test
public void testNotebookCreateWithName() throws IOException {
String noteName = "Test note name";
@ -405,7 +439,52 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
//cleanup
ZeppelinServer.notebook.removeNote(note.getId());
}
@Test
public void testRunParagraphWithParams() throws IOException, InterruptedException {
LOG.info("testRunParagraphWithParams");
// Create note to run test.
Note note = ZeppelinServer.notebook.createNote();
assertNotNull("can't create new note", note);
note.setName("note for run test");
Paragraph paragraph = note.addParagraph();
Map config = paragraph.getConfig();
config.put("enabled", true);
paragraph.setConfig(config);
paragraph.setText("%spark\nval param = z.input(\"param\").toString\nprintln(param)");
note.persist();
String noteID = note.getId();
note.runAll();
// wait until job is finished or timeout.
int timeout = 1;
while (!paragraph.isTerminated()) {
Thread.sleep(1000);
if (timeout++ > 120) {
LOG.info("testRunParagraphWithParams timeout job.");
break;
}
}
// Call Run paragraph REST API
PostMethod postParagraph = httpPost("/notebook/job/" + noteID + "/" + paragraph.getId(),
"{\"params\": {\"param\": \"hello\", \"param2\": \"world\"}}");
assertThat("test paragraph run:", postParagraph, isAllowed());
postParagraph.releaseConnection();
Thread.sleep(1000);
Note retrNote = ZeppelinServer.notebook.getNote(noteID);
Paragraph retrParagraph = retrNote.getParagraph(paragraph.getId());
Map<String, Object> params = retrParagraph.settings.getParams();
assertEquals("hello", params.get("param"));
assertEquals("world", params.get("param2"));
//cleanup
ZeppelinServer.notebook.removeNote(note.getId());
}
@Test
public void testCronJobs() throws InterruptedException, IOException{
// create a note and a paragraph

View file

@ -20,7 +20,7 @@ angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootSc
var init = function() {
$scope.asIframe = (($window.location.href.indexOf('asIframe') > -1) ? true : false);
};
init();
$rootScope.$on('setIframe', function(event, data) {
@ -36,10 +36,13 @@ angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootSc
event.preventDefault();
}
});
// Set The lookAndFeel to default on every page
$rootScope.$on('$routeChangeStart', function(event, next, current) {
$rootScope.$broadcast('setLookAndFeel', 'default');
});
BootstrapDialog.defaultOptions.onshown = function() {
angular.element('#' + this.id).find('.btn:last').focus();
};
});

View file

@ -275,10 +275,6 @@ kbd {
border-radius: 2px;
}
.home {
min-height: 400px;
}
/*
ngToast Style
*/

View file

@ -20,6 +20,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.interpreterSettings = [];
$scope.availableInterpreters = {};
$scope.showAddNewSetting = false;
$scope._ = _;
var getInterpreterSettings = function() {
$http.get(baseUrlSrv.getRestApiBase()+'/interpreter/setting').
@ -56,6 +57,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.updateInterpreterSetting = function(settingId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to update this interpreter and restart with new settings?',
callback: function(result) {
@ -88,6 +90,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.removeInterpreterSetting = function(settingId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to delete this interpreter setting?',
callback: function(result) {
@ -125,6 +128,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.restartInterpreterSetting = function(settingId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to restart this interpreter?',
callback: function(result) {
@ -145,6 +149,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
$scope.addNewInterpreterSetting = function() {
if (!$scope.newInterpreterSetting.name || !$scope.newInterpreterSetting.group) {
BootstrapDialog.alert({
closable: true,
title: 'Add interpreter',
message: 'Please determine name and interpreter'
});
@ -153,6 +158,7 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
if (_.findIndex($scope.interpreterSettings, { 'name': $scope.newInterpreterSetting.name }) >= 0) {
BootstrapDialog.alert({
closable: true,
title: 'Add interpreter',
message: 'Name ' + $scope.newInterpreterSetting.name + ' already exists'
});
@ -216,6 +222,9 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
var index = _.findIndex($scope.interpreterSettings, { 'id': settingId });
var setting = $scope.interpreterSettings[index];
if (!setting.propertyKey || setting.propertyKey === '') {
return;
}
setting.properties[setting.propertyKey] = setting.propertyValue;
emptyNewProperty(setting);
}

View file

@ -62,10 +62,6 @@
overflow-y: auto;
}
.interpreter table tr {
height : 45px;
}
.interpreterSettingAdd {
margin : 5px 5px 5px 5px;
padding : 10px 10px 10px 10px;
@ -74,3 +70,17 @@
.editable-wrap {
width : 100%;
}
.interpreter h5 {
font-weight: bold;
}
.new_h3 {
margin-top: 1px;
padding-top: 7px;
float: left;
}
.empty-properties-message {
color: #666;
}

View file

@ -15,7 +15,7 @@ limitations under the License.
<div class="header">
<div class="row">
<div class="col-md-12">
<h3 class="new_h3" style="float:left">
<h3 class="new_h3">
Interpreters
</h3>
<span class="btn btn-default fa fa-plus"
@ -65,17 +65,21 @@ limitations under the License.
</span>
</div>
</div>
<br />
<div class="row interpreter">
<div class="col-md-12">
<b>Properties</b>
<div ng-show="_.isEmpty(setting.properties) || valueform.$hidden" class="col-md-12 empty-properties-message">
<em>Currently there are no properties set for this interpreter</em>
</div>
<div class="col-md-12" ng-show="!_.isEmpty(setting.properties) || valueform.$visible">
<h5>Properties</h5>
<table class="table table-striped">
<tr>
<th style="width:30%">name</th>
<th>value</th>
<th ng-if="valueform.$visible">action</th>
</tr>
<tr ng-repeat="(key, value) in setting.properties">
<thead>
<tr>
<th style="width:30%">name</th>
<th>value</th>
<th ng-if="valueform.$visible">action</th>
</tr>
</thead>
<tr ng-repeat="(key, value) in setting.properties" >
<td>{{key}}</td>
<td>
<span editable-textarea="setting.properties[key]" e-form="valueform" e-msd-elastic>

View file

@ -14,14 +14,14 @@ limitations under the License.
<div class="noteAction" ng-show="note.id && !paragraphUrl">
<h3>
<input type="text" pu-elastic-input class="form-control2" placeholder="{{note.name || 'Note ' + note.id}}" style="min-width: 200px; max-width: 600px;"
ng-show="showEditor" ng-model="note.name" ng-blur="sendNewName()" ng-enter="sendNewName()" ng-escape="note.name = oldName; showEditor = false" focus-if="showEditor" />
ng-show="showEditor" ng-model="note.name" ng-blur="sendNewName();showEditor = false;" ng-enter="sendNewName();showEditor = false;" ng-escape="note.name = oldName; showEditor = false" focus-if="showEditor" />
<p class="form-control-static2" ng-click="showEditor = true; oldName = note.name" ng-show="!showEditor">{{note.name || 'Note ' + note.id}}</p>
<span class="labelBtn btn-group">
<button type="button"
class="btn btn-default btn-xs"
ng-click="runNote()"
ng-class="{'disabled':isNoteRunning()}"
tooltip-placement="bottom" tooltip="Run all the notes">
tooltip-placement="bottom" tooltip="Run all paragraphs">
<i class="icon-control-play"></i>
</button>
<button type="button"

View file

@ -74,6 +74,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
/** 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) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to delete this notebook?',
callback: function(result) {
@ -94,6 +95,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
//Clone note
$scope.cloneNote = function(noteId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to clone this notebook?',
callback: function(result) {
@ -107,6 +109,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
$scope.runNote = function() {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Run all paragraphs?',
callback: function(result) {
@ -130,6 +133,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
$scope.clearAllParagraphOutput = function() {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to clear all output?',
callback: function(result) {
@ -236,7 +240,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
/** Update the note name */
$scope.sendNewName = function() {
$scope.showEditor = false;
if ($scope.note.name) {
websocketMsgSrv.updateNotebook($scope.note.id, $scope.note.name, $scope.note.config);
}
@ -314,11 +317,11 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
$scope.$on('insertParagraph', function(event, paragraphId, position) {
var newIndex = -1;
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if ( $scope.note.paragraphs[i].id === paragraphId ) {
if ( $scope.note.paragraphs[i].id === paragraphId ) {
//determine position of where to add new paragraph; default is below
if ( position === 'above' ) {
if ( position === 'above' ) {
newIndex = i;
} else {
} else {
newIndex = i+1;
}
break;
@ -498,16 +501,19 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro
$scope.closeSetting = function() {
if (isSettingDirty()) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Changes will be discarded',
message: 'Changes will be discarded.',
callback: function(result) {
if (result) {
$scope.$apply(function () {
$scope.$apply(function() {
$scope.showSetting = false;
});
}
}
});
} else {
$scope.showSetting = false;
}
};

View file

@ -104,11 +104,6 @@
color: #333333;
}
.new_h3 {
margin-top: 1px;
padding-top: 7px;
}
.form-control2 {
width: 100%;
height: 40px;

View file

@ -298,6 +298,7 @@ angular.module('zeppelinWebApp')
$scope.removeParagraph = function() {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to delete this paragraph?',
callback: function(result) {
@ -941,14 +942,18 @@ angular.module('zeppelinWebApp')
var renderTable = function() {
var html = '';
html += '<table class="table table-hover table-condensed">';
html += '<table class="table table-hover table-condensed" style="top: 0; position: absolute;">';
html += ' <thead>';
html += ' <tr style="background-color: #F6F6F6; font-weight: bold;">';
for (var c in $scope.paragraph.result.columnNames) {
html += '<th>'+$scope.paragraph.result.columnNames[c].name+'</th>';
for (var titleIndex in $scope.paragraph.result.columnNames) {
html += '<th>'+$scope.paragraph.result.columnNames[titleIndex].name+'</th>';
}
html += ' </tr>';
html += ' </thead>';
html += '</table>';
html += '<table class="table table-hover table-condensed" style="margin-top: 31px;">';
for (var r in $scope.paragraph.result.msgTable) {
var row = $scope.paragraph.result.msgTable[r];

View file

@ -32,17 +32,16 @@
*/
.paragraph .text {
display: block;
unicode-bidi: embed;
display: block !important;
margin: 0 0 0 !important;
font-family: "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;
font-size: 12px !important;
line-height: 1.42857143 !important;
margin: 0 0 5px !important;
padding-top: 2px;
unicode-bidi: embed;
white-space: pre-wrap;
word-break: break-all !important;
word-wrap: break-word !important;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
font-size: 12px !important;
margin-bottom: 5px !important;
padding-top: 2px;
}
.paragraph table {
@ -404,3 +403,8 @@
.dropdown-menu > li:first-child > a:hover {
background-color: transparent;
}
table.table-striped {
border-top: 1px solid #ddd;
margin-top: 20px;
}

View file

@ -85,7 +85,10 @@ public class VFSNotebookRepoTest implements JobListenerFactory{
@After
public void tearDown() throws Exception {
FileUtils.deleteDirectory(mainZepDir);
//FileUtils.deleteDirectory(mainZepDir);
if (!FileUtils.deleteQuietly(mainZepDir)) {
logger.error("Failed to delete {} ", mainZepDir.getName());
}
}
@Test