mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Merge remote-tracking branch 'upstream/master' into scalding
This commit is contained in:
commit
8944b0c3ae
21 changed files with 614 additions and 124 deletions
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 :
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -275,10 +275,6 @@ kbd {
|
|||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.home {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/*
|
||||
ngToast Style
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -104,11 +104,6 @@
|
|||
color: #333333;
|
||||
}
|
||||
|
||||
.new_h3 {
|
||||
margin-top: 1px;
|
||||
padding-top: 7px;
|
||||
}
|
||||
|
||||
.form-control2 {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue