diff --git a/bin/common.sh b/bin/common.sh index 5f20436c47f..1a8e2d7c0df 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -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}" diff --git a/bin/interpreter.sh b/bin/interpreter.sh index 464cd9ce1d9..ba7f0172362 100755 --- a/bin/interpreter.sh +++ b/bin/interpreter.sh @@ -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}" diff --git a/docs/rest-api/rest-interpreter.md b/docs/rest-api/rest-interpreter.md index d8523402847..6e41fe87716 100644 --- a/docs/rest-api/rest-interpreter.md +++ b/docs/rest-api/rest-interpreter.md @@ -361,3 +361,37 @@ limitations under the License. + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Restart an interpreter
DescriptionThis ```PUT``` method restart the given interpreter id.
URL```http://[zeppelin-server]:[zeppelin-port]/api/interpreter/setting/restart/[interpreter ID]```
Success code200
Fail code 500
sample JSON response + +
{"status":"OK"}
+
diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 8293c4f176e..bbecc5c93ae 100644 --- a/docs/rest-api/rest-notebook.md +++ b/docs/rest-api/rest-notebook.md @@ -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) @@ -43,7 +43,7 @@ limitations under the License. Description - This ```GET``` method list the available notebooks on your server. + This ```GET``` method lists the available notebooks on your server. Notebook JSON contains the ```name``` and ```id``` of all notebooks. @@ -75,8 +75,8 @@ limitations under the License. Description - 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. + 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. @@ -129,7 +129,7 @@ limitations under the License. Description - This ```DELETE``` method delete a notebook by the given notebook id. + This ```DELETE``` method deletes a notebook by the given notebook id. @@ -160,9 +160,9 @@ limitations under the License. Description - This ```POST``` method clone a notebook by the given id and create a new notebook using the given name + 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. @@ -197,7 +197,7 @@ limitations under the License. Description - This ```POST``` method run all paragraph in the given notebook id. + This ```POST``` method runs all paragraph in the given notebook id. @@ -228,7 +228,7 @@ limitations under the License. Description - This ```DELETE``` method stop all paragraph in the given notebook id. + This ```DELETE``` method stops all paragraph in the given notebook id. @@ -261,7 +261,7 @@ limitations under the License. Description - This ```GET``` method get all paragraph status by the given notebook id. + 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. @@ -293,7 +293,7 @@ limitations under the License. Description - This ```POST``` method run the paragraph by given notebook and paragraph id. + This ```POST``` method runs the paragraph by given notebook and paragraph id. @@ -324,7 +324,7 @@ limitations under the License. Description - This ```DELETE``` method stop the paragraph by given notebook and paragraph id. + This ```DELETE``` method stops the paragraph by given notebook and paragraph id. @@ -355,7 +355,7 @@ limitations under the License. Description - This ```POST``` method add cron job by the given notebook id. + This ```POST``` method adds cron job by the given notebook id. @@ -390,7 +390,7 @@ limitations under the License. Description - This ```DELETE``` method remove cron job by the given notebook id. + This ```DELETE``` method removes cron job by the given notebook id. @@ -416,13 +416,13 @@ limitations under the License. - + - diff --git a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java index 273c89736ec..0bfad6a0b40 100644 --- a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java +++ b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java @@ -58,6 +58,11 @@ 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 int getProgress(InterpreterContext context) { return sparkInterpreter.getProgress(context); } + @Override public List completion(String buf, int cursor) { - // not supported - return new LinkedList(); + 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(); + } + + 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(); + } + } + } + + if (statementError) { + return new LinkedList(); + } + 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; diff --git a/spark/src/main/resources/python/zeppelin_pyspark.py b/spark/src/main/resources/python/zeppelin_pyspark.py index 1b17772b736..7ab0be99938 100644 --- a/spark/src/main/resources/python/zeppelin_pyspark.py +++ b/spark/src/main/resources/python/zeppelin_pyspark.py @@ -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 @@ def isAutoConvertEnabled(self): 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 @@ def isImportAllPackageUnderSparkSql(self): sqlc = SQLContext(sc, intp.getSQLContext()) sqlContext = sqlc +completion = PySparkCompletion() z = PyZeppelinContext(intp.getZeppelinContext()) while True : diff --git a/zeppelin-web/src/app/app.controller.js b/zeppelin-web/src/app/app.controller.js index 3f51330e315..2c302b542d9 100644 --- a/zeppelin-web/src/app/app.controller.js +++ b/zeppelin-web/src/app/app.controller.js @@ -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(); + }; }); diff --git a/zeppelin-web/src/app/interpreter/interpreter.controller.js b/zeppelin-web/src/app/interpreter/interpreter.controller.js index ea62b16b927..396a552ef47 100644 --- a/zeppelin-web/src/app/interpreter/interpreter.controller.js +++ b/zeppelin-web/src/app/interpreter/interpreter.controller.js @@ -57,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) { @@ -89,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) { @@ -126,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) { @@ -146,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' }); @@ -154,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' }); diff --git a/zeppelin-web/src/app/notebook/notebook-actionBar.html b/zeppelin-web/src/app/notebook/notebook-actionBar.html index 340273593db..dc99e1d1bb7 100644 --- a/zeppelin-web/src/app/notebook/notebook-actionBar.html +++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html @@ -14,7 +14,7 @@

+ 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" />

{{note.name || 'Note ' + note.id}}

'; } html += ' '; html += ' '; + html += '
Get clone jobGet cron job
DescriptionThis ```GET``` method get cron job expression of given notebook id. - The body field of the returned JSON contain the cron expression. + This ```GET``` method gets cron job expression of given notebook id. + The body field of the returned JSON contains the cron expression.
'+$scope.paragraph.result.columnNames[titleIndex].name+'
'; + + html += ''; for (var r in $scope.paragraph.result.msgTable) { var row = $scope.paragraph.result.msgTable[r]; @@ -971,6 +976,8 @@ angular.module('zeppelinWebApp') if ($scope.paragraph.result.msgTable.length > 10000) { angular.element('#p' + $scope.paragraph.id + '_table').css('overflow', 'scroll'); } else { + angular.element('#p' + $scope.paragraph.id + '_table').css('position', 'relative'); + angular.element('#p' + $scope.paragraph.id + '_table').css('height', '100%'); angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar(); } diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css index bdf09c2c56a..7c7e1338f81 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css @@ -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 {