diff --git a/docs/development/writingzeppelininterpreter.md b/docs/development/writingzeppelininterpreter.md index d40101b6555..225e41c9fcf 100644 --- a/docs/development/writingzeppelininterpreter.md +++ b/docs/development/writingzeppelininterpreter.md @@ -42,7 +42,7 @@ In 'Separate Interpreter(scoped / isolated) for each note' mode which you can se Creating a new interpreter is quite simple. Just extend [org.apache.zeppelin.interpreter](https://github.com/apache/zeppelin/blob/master/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java) abstract class and implement some methods. You can include `org.apache.zeppelin:zeppelin-interpreter:[VERSION]` artifact in your build system. And you should put your jars under your interpreter directory with a specific directory name. Zeppelin server reads interpreter directories recursively and initializes interpreters including your own interpreter. -There are three locations where you can store your interpreter group, name and other information. Zeppelin server tries to find the location below. Next, Zeppelin tries to find `interpreter-setting.json` in your interpreter jar. +There are three locations where you can store your interpreter group, name and other information. Zeppelin server tries to find the location below. Next, Zeppelin tries to find `interpreter-setting.json` in your interpreter jar. ``` {ZEPPELIN_INTERPRETER_DIR}/{YOUR_OWN_INTERPRETER_DIR}/interpreter-setting.json @@ -68,12 +68,15 @@ Here is an example of `interpreter-setting.json` on your own interpreter. "propertyName": null, "defaultValue": "property2DefaultValue", "description": "Property 2 description" - }, ... + },... + }, + "editor": { + "language": "your-syntax-highlight-language" } }, { ... - } + } ] ``` @@ -96,15 +99,20 @@ some interpreter specific code... ``` ## Programming Languages for Interpreter -If the interpreter uses a specific programming language ( like Scala, Python, SQL ), it is generally recommended to add a syntax highlighting supported for that to the notebook paragraph editor. +If the interpreter uses a specific programming language (like Scala, Python, SQL), it is generally recommended to add a syntax highlighting supported for that to the notebook paragraph editor. To check out the list of languages supported, see the `mode-*.js` files under `zeppelin-web/bower_components/ace-builds/src-noconflict` or from [github.com/ajaxorg/ace-builds](https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict). If you want to add a new set of syntax highlighting, 1. Add the `mode-*.js` file to [zeppelin-web/bower.json](https://github.com/apache/zeppelin/blob/master/zeppelin-web/bower.json) ( when built, [zeppelin-web/src/index.html](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/index.html) will be changed automatically. ). -2. Add to the list of `editorMode` in [zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js) - it follows the pattern 'ace/mode/x' where x is the name. -3. Add to the code that checks for `%` prefix and calls `session.setMode(editorMode.x)` in `setParagraphMode` located in [zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js). +2. Add `editor` object to `interpreter-setting.json` file. If you want to set your language to java, add: + + ``` + "editor": { + "language": "java" + } + ``` ## Install your interpreter binary @@ -216,4 +224,3 @@ We welcome contribution to a new interpreter. Please follow these few steps: - Add documentation on how to use your interpreter under `docs/interpreter/`. Follow the Markdown style as this [example](https://github.com/apache/zeppelin/blob/master/docs/interpreter/elasticsearch.md). Make sure you list config settings and provide working examples on using your interpreter in code boxes in Markdown. Link to images as appropriate (images should go to `docs/assets/themes/zeppelin/img/docs-img/`). And add a link to your documentation in the navigation menu (`docs/_includes/themes/zeppelin/_navigation.html`). - Most importantly, ensure licenses of the transitive closure of all dependencies are list in [license file](https://github.com/apache/zeppelin/blob/master/zeppelin-distribution/src/bin_license/LICENSE). - Commit your changes and open a [Pull Request](https://github.com/apache/zeppelin/pulls) on the project [Mirror on GitHub](https://github.com/apache/zeppelin); check to make sure Travis CI build is passing. - diff --git a/flink/src/main/resources/interpreter-setting.json b/flink/src/main/resources/interpreter-setting.json index 067e7b84e44..e9bd126827f 100644 --- a/flink/src/main/resources/interpreter-setting.json +++ b/flink/src/main/resources/interpreter-setting.json @@ -16,6 +16,9 @@ "defaultValue": "6123", "description": "port of running JobManager." } + }, + "editor": { + "language": "scala" } } ] diff --git a/jdbc/src/main/resources/interpreter-setting.json b/jdbc/src/main/resources/interpreter-setting.json index 289d4f89ba6..abdb7190d07 100644 --- a/jdbc/src/main/resources/interpreter-setting.json +++ b/jdbc/src/main/resources/interpreter-setting.json @@ -154,6 +154,9 @@ "defaultValue": "org.postgresql.Driver", "description": "" } + }, + "editor": { + "language": "sql" } } ] diff --git a/livy/src/main/resources/interpreter-setting.json b/livy/src/main/resources/interpreter-setting.json index 2c1a0beceba..70404321304 100644 --- a/livy/src/main/resources/interpreter-setting.json +++ b/livy/src/main/resources/interpreter-setting.json @@ -82,6 +82,9 @@ "defaultValue": "", "description": "Kerberos keytab to authenticate livy" } + }, + "editor": { + "language": "scala" } }, { @@ -100,6 +103,9 @@ "defaultValue": "false", "description": "Execute multiple SQL concurrently if set true." } + }, + "editor": { + "language": "sql" } }, { @@ -107,6 +113,9 @@ "name": "pyspark", "className": "org.apache.zeppelin.livy.LivyPySparkInterpreter", "properties": { + }, + "editor": { + "language": "python" } }, { @@ -114,6 +123,9 @@ "name": "sparkr", "className": "org.apache.zeppelin.livy.LivySparkRInterpreter", "properties": { + }, + "editor": { + "language": "r" } } -] \ No newline at end of file +] diff --git a/python/src/main/resources/interpreter-setting.json b/python/src/main/resources/interpreter-setting.json index 868b54747c8..f1cd5711daf 100644 --- a/python/src/main/resources/interpreter-setting.json +++ b/python/src/main/resources/interpreter-setting.json @@ -16,6 +16,9 @@ "defaultValue": "1000", "description": "Max number of dataframe rows to display." } + }, + "editor": { + "language": "python" } }, { diff --git a/shell/src/main/resources/interpreter-setting.json b/shell/src/main/resources/interpreter-setting.json index 5e9a051a2eb..db34607b286 100644 --- a/shell/src/main/resources/interpreter-setting.json +++ b/shell/src/main/resources/interpreter-setting.json @@ -10,6 +10,9 @@ "defaultValue": "60000", "description": "Shell command time out in millisecs. Default = 60000" } + }, + "editor": { + "language": "sh" } } -] \ No newline at end of file +] diff --git a/spark/src/main/resources/interpreter-setting.json b/spark/src/main/resources/interpreter-setting.json index 2343a0f9745..2d80bba9ceb 100644 --- a/spark/src/main/resources/interpreter-setting.json +++ b/spark/src/main/resources/interpreter-setting.json @@ -54,6 +54,9 @@ "defaultValue": "local[*]", "description": "Spark master uri. ex) spark://masterhost:7077" } + }, + "editor": { + "language": "scala" } }, { @@ -85,6 +88,9 @@ "defaultValue": "true", "description": "Import implicits, UDF collection, and sql if set true. true by default." } + }, + "editor": { + "language": "sql" } }, { @@ -104,6 +110,9 @@ "defaultValue": "spark-packages,http://dl.bintray.com/spark-packages/maven,false;", "description": "A list of 'id,remote-repository-URL,is-snapshot;' for each remote repository." } + }, + "editor": { + "language": "scala" } }, { @@ -117,6 +126,9 @@ "defaultValue": "python", "description": "Python command to run pyspark with" } + }, + "editor": { + "language": "python" } } ] diff --git a/spark/src/main/sparkr-resources/interpreter-setting.json b/spark/src/main/sparkr-resources/interpreter-setting.json index 4902baf9f76..61f984a5ea4 100644 --- a/spark/src/main/sparkr-resources/interpreter-setting.json +++ b/spark/src/main/sparkr-resources/interpreter-setting.json @@ -54,6 +54,9 @@ "defaultValue": "local[*]", "description": "Spark master uri. ex) spark://masterhost:7077" } + }, + "editor": { + "language": "scala" } }, { @@ -85,6 +88,9 @@ "defaultValue": "true", "description": "Import implicits, UDF collection, and sql if set true. true by default." } + }, + "editor": { + "language": "sql" } }, { @@ -104,6 +110,9 @@ "defaultValue": "spark-packages,http://dl.bintray.com/spark-packages/maven,false;", "description": "A list of 'id,remote-repository-URL,is-snapshot;' for each remote repository." } + }, + "editor": { + "language": "scala" } }, { @@ -117,6 +126,9 @@ "defaultValue": "python", "description": "Python command to run pyspark with" } + }, + "editor": { + "language": "python" } }, { @@ -148,6 +160,9 @@ "defaultValue": "out.format = 'html', comment = NA, echo = FALSE, results = 'asis', message = F, warning = F", "description": "" } + }, + "editor": { + "language": "r" } } ] diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java index 07f9cbabcad..cdd2f6338b3 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/Interpreter.java @@ -251,6 +251,7 @@ public static class RegisteredInterpreter { private String className; private boolean defaultInterpreter; private Map properties; + private Map editor; private String path; public RegisteredInterpreter(String name, String group, String className, @@ -266,6 +267,7 @@ public RegisteredInterpreter(String name, String group, String className, this.className = className; this.defaultInterpreter = defaultInterpreter; this.properties = properties; + this.editor = new HashMap<>(); } public String getName() { @@ -292,6 +294,10 @@ public Map getProperties() { return properties; } + public Map getEditor() { + return editor; + } + public void setPath(String path) { this.path = path; } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 853c0d5c524..d0fd0f5853e 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -226,6 +226,9 @@ public void onMessage(NotebookSocket conn, String msg) { case LIST_UPDATE_NOTEBOOK_JOBS: unicastUpdateNotebookJobInfo(conn, messagereceived); break; + case EDITOR_SETTING: + getEditorSetting(conn, notebook, messagereceived); + break; default: break; } @@ -1259,7 +1262,7 @@ public void afterStatusChange(Job job, Status before, Status after) { } /** - * This callback is for praragraph that runs on RemoteInterpreterProcess + * This callback is for paragraph that runs on RemoteInterpreterProcess * @param paragraph * @param out * @param output @@ -1367,11 +1370,24 @@ public void onRemove(String interpreterGroupId, String name, String noteId, Stri if (id.equals(interpreterGroupId)) { broadcast( note.id(), - new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put( - "noteId", noteId).put("paragraphId", paragraphId)); + new Message(OP.ANGULAR_OBJECT_REMOVE) + .put("name", name) + .put("noteId", noteId) + .put("paragraphId", paragraphId)); } } } } + + private void getEditorSetting(NotebookSocket conn, Notebook notebook, Message fromMessage) + throws IOException { + String replName = (String) fromMessage.get("magic"); + String noteId = getOpenNoteId(conn); + Note note = notebook.getNote(noteId); + Message resp = new Message(OP.EDITOR_SETTING); + resp.put("editor", note.getEditorSetting(replName)); + conn.send(serializeMessage(resp)); + return; + } } diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 5d849b3490d..f0f9443817b 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -24,7 +24,7 @@ "angular-elastic": "~2.4.2", "angular-elastic-input": "~2.2.0", "angular-xeditable": "0.1.12", - "highlightjs": "^9.2.0", + "highlightjs": "^9.4.0", "lodash": "~3.9.3", "angular-filter": "~0.5.4", "ngtoast": "~2.0.0", @@ -59,7 +59,7 @@ "highlight.pack.js", "styles/github.css" ], - "version": "8.4.0", + "version": "9.4.0", "name": "highlightjs" } } diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index fcf609eac2c..d2939d37ebd 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -16,7 +16,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $rootScope, $route, $window, $routeParams, $location, $timeout, $compile, - $http, websocketMsgSrv, baseUrlSrv, ngToast, + $http, $q, websocketMsgSrv, baseUrlSrv, ngToast, SaveAsService) { var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.parentNote = null; @@ -80,12 +80,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r var angularObjectRegistry = {}; var editorModes = { - 'ace/mode/python': /^%(\w*\.)?(pyspark|python)\s*$/, - 'ace/mode/scala': /^%(\w*\.)?spark\s*$/, - 'ace/mode/r': /^%(\w*\.)?(r|sparkr|knitr)\s*$/, 'ace/mode/sql': /^%(\w*\.)?\wql/, - 'ace/mode/markdown': /^%md/, - 'ace/mode/sh': /^%sh/ + 'ace/mode/markdown': /^%md/ }; // Controller init @@ -526,7 +522,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r $scope.startSaveTimer(); $timeout(function() { - $scope.setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); + setParagraphMode($scope.editor.getSession(), $scope.dirtyText, $scope.editor.getCursorPosition()); }); }; @@ -563,37 +559,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r // not applying emacs key binding while the binding override Ctrl-v. default behavior of paste text on windows. } - $scope.setParagraphMode = function(session, paragraphText, pos) { - // Evaluate the mode only if the first 30 characters of the paragraph have been modified or the the position is undefined. - if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30)) { - // If paragraph loading, use config value if exists - if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) { - session.setMode($scope.paragraph.config.editorMode); - } else { - // Defaults to spark mode - var newMode = 'ace/mode/scala'; - // Test first against current mode - var oldMode = session.getMode().$id; - if (!editorModes[oldMode] || !editorModes[oldMode].test(paragraphText)) { - for (var key in editorModes) { - if (key !== oldMode) { - if (editorModes[key].test(paragraphText)) { - $scope.paragraph.config.editorMode = key; - session.setMode(key); - return true; - } - } - } - $scope.paragraph.config.editorMode = newMode; - session.setMode(newMode); - } - } - } - }; - var remoteCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { - if (!$scope.editor.isFocused()) { return;} + if (!$scope.editor.isFocused()) { + return; + } pos = session.getTextRange(new Range(0, 0, pos.row, pos.column)).length; var buf = session.getValue(); @@ -647,7 +617,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r autoAdjustEditorHeight(_editor.container.id); }); - $scope.setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); + setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); // autocomplete on '.' /* @@ -710,6 +680,70 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } }; + var getEditorSetting = function(interpreterName) { + var deferred = $q.defer(); + websocketMsgSrv.getEditorSetting(interpreterName); + $timeout( + $scope.$on('editorSetting', function(event, data) { + deferred.resolve(data); + } + ), 1000); + return deferred.promise; + }; + + var setParagraphMode = function(session, paragraphText, pos) { + // Evaluate the mode only if the the position is undefined + // or the first 30 characters of the paragraph have been modified + // or cursor position is at beginning of second line.(in case user hit enter after typing %magic) + if ((typeof pos === 'undefined') || (pos.row === 0 && pos.column < 30) || (pos.row === 1 && pos.column === 0)) { + // If paragraph loading, use config value if exists + if ((typeof pos === 'undefined') && $scope.paragraph.config.editorMode) { + session.setMode($scope.paragraph.config.editorMode); + } else { + var magic; + // set editor mode to default interpreter syntax if paragraph text doesn't start with '%' + if (!paragraphText.startsWith('%')) { + magic = $scope.$parent.interpreterBindings[0].group; + } else { + var replNameRegexp = /%(.+?)\s/g; + var match = replNameRegexp.exec(paragraphText); + if (match) { + magic = match[1]; + } + } + if (magic) { + var promise = getEditorSetting(magic); + promise.then(function(editorSetting) { + if (!_.isEmpty(editorSetting.editor)) { + var mode = 'ace/mode/' + editorSetting.editor.language; + $scope.paragraph.config.editorMode = mode; + session.setMode(mode); + } + // TODO(mina): remove else condition once register machanism of all interpreter changed + else { + var newMode = 'ace/mode/scala'; + // Test first against current mode + var oldMode = session.getMode().$id; + if (!editorModes[oldMode] || !editorModes[oldMode].test(paragraphText)) { + for (var key in editorModes) { + if (key !== oldMode) { + if (editorModes[key].test(paragraphText)) { + $scope.paragraph.config.editorMode = key; + session.setMode(key); + return true; + } + } + } + $scope.paragraph.config.editorMode = newMode; + session.setMode(newMode); + } + } + }); + } + } + } + }; + var autoAdjustEditorHeight = function(id) { var editor = $scope.editor; var height = editor.getSession().getScreenLength() * editor.renderer.lineHeight + @@ -1102,11 +1136,11 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r } var chartEl = d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg') - .attr('height', $scope.paragraph.config.graph.height) - .datum(d3g) - .transition() - .duration(animationDuration) - .call($scope.chart[type]); + .attr('height', $scope.paragraph.config.graph.height) + .datum(d3g) + .transition() + .duration(animationDuration) + .call($scope.chart[type]); d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg').style.height = height + 'px'; nv.utils.windowResize($scope.chart[type].update); }; @@ -1946,7 +1980,6 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r var noteId = $route.current.pathParams.noteId; $http.get(baseUrlSrv.getRestApiBase() + '/helium/suggest/' + noteId + '/' + $scope.paragraph.id) .success(function(data, status, headers, config) { - console.log('Suggested apps %o', data); $scope.suggestion = data.body; }) .error(function(err, status, headers, config) { diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js index 2fb5003ba15..d26b7266ad4 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js +++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js @@ -76,8 +76,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', action: function(dialog) { dialog.close(); angular.element('#loginModal').modal({ - show: 'true' - }); + show: 'true' + }); } }, { label: 'Cancel', @@ -97,6 +97,8 @@ angular.module('zeppelinWebApp').factory('websocketEvents', $rootScope.$broadcast('updateProgress', data); } else if (op === 'COMPLETION_LIST') { $rootScope.$broadcast('completionList', data); + } else if (op === 'EDITOR_SETTING') { + $rootScope.$broadcast('editorSetting', data); } else if (op === 'ANGULAR_OBJECT_UPDATE') { $rootScope.$broadcast('angularObjectUpdate', data); } else if (op === 'ANGULAR_OBJECT_REMOVE') { diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js index 698b8ff8100..a2f9f072d14 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js +++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js @@ -161,6 +161,15 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, }); }, + getEditorSetting: function(replName) { + websocketEvents.sendNewEvent({ + op: 'EDITOR_SETTING', + data: { + magic: replName + } + }); + }, + isConnected: function() { return websocketEvents.isConnected(); }, diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index 9106cf5f17c..ca66efd43f1 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -650,6 +650,21 @@ public String getLastInterpreterName() { return getInterpreterName(getLastReplName()); } + public Interpreter getRepl(String name) { + return factory.getInterpreter(id(), name); + } + + public Map getEditorSetting(String replName) { + Interpreter intp = getRepl(replName); + Map editor = new HashMap<>(); + try { + editor = intp.findRegisteredInterpreterByClassName(intp.getClassName()).getEditor(); + } catch (NullPointerException e) { + editor.put("language", "text"); + } + return editor; + } + @Override public void beforeStatusChange(Job job, Status before, Status after) { if (jobListenerFactory != null) { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java index 73174061cad..063b56ba947 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java @@ -202,7 +202,7 @@ public static String getScriptBody(String text) { } public Interpreter getRepl(String name) { - return factory.getInterpreter(note.getId(), name); + return note.getRepl(name); } public Interpreter getCurrentRepl() { diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java index 08b32359226..9463841f3fe 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java @@ -21,43 +21,43 @@ import java.util.Map; /** - * Zeppelin websocker massage template class. + * Zeppelin websocket massage template class. */ public class Message { /** * Representation of event type. */ public static enum OP { - GET_HOME_NOTE, // [c-s] load note for home screen + GET_HOME_NOTE, // [c-s] load note for home screen - GET_NOTE, // [c-s] client load note - // @param id note id + GET_NOTE, // [c-s] client load note + // @param id note id - NOTE, // [s-c] note info - // @param note serlialized Note object + NOTE, // [s-c] note info + // @param note serlialized Note object - PARAGRAPH, // [s-c] paragraph info - // @param paragraph serialized paragraph object + PARAGRAPH, // [s-c] paragraph info + // @param paragraph serialized paragraph object - PROGRESS, // [s-c] progress update - // @param id paragraph id - // @param progress percentage progress - - NEW_NOTE, // [c-s] create new notebook - DEL_NOTE, // [c-s] delete notebook - // @param id note id - CLONE_NOTE, // [c-s] clone new notebook - // @param id id of note to clone - // @param name name fpor the cloned note - IMPORT_NOTE, // [c-s] import notebook - // @param object notebook + PROGRESS, // [s-c] progress update + // @param id paragraph id + // @param progress percentage progress + + NEW_NOTE, // [c-s] create new notebook + DEL_NOTE, // [c-s] delete notebook + // @param id note id + CLONE_NOTE, // [c-s] clone new notebook + // @param id id of note to clone + // @param name name fpor the cloned note + IMPORT_NOTE, // [c-s] import notebook + // @param object notebook NOTE_UPDATE, - RUN_PARAGRAPH, // [c-s] run paragraph - // @param id paragraph id - // @param paragraph paragraph content.ie. script - // @param config paragraph config - // @param params paragraph params + RUN_PARAGRAPH, // [c-s] run paragraph + // @param id paragraph id + // @param paragraph paragraph content.ie. script + // @param config paragraph config + // @param params paragraph params COMMIT_PARAGRAPH, // [c-s] commit paragraph // @param id paragraph id @@ -69,60 +69,64 @@ public static enum OP { CANCEL_PARAGRAPH, // [c-s] cancel paragraph run // @param id paragraph id - MOVE_PARAGRAPH, // [c-s] move paragraph order - // @param id paragraph id - // @param index index the paragraph want to go + MOVE_PARAGRAPH, // [c-s] move paragraph order + // @param id paragraph id + // @param index index the paragraph want to go INSERT_PARAGRAPH, // [c-s] create new paragraph below current paragraph // @param target index - COMPLETION, // [c-s] ask completion candidates - // @param id - // @param buf current code - // @param cursor cursor position in code + EDITOR_SETTING, // [c-s] ask paragraph editor setting + // @param magic magic keyword written in paragraph + // ex) spark.spark or spark + + COMPLETION, // [c-s] ask completion candidates + // @param id + // @param buf current code + // @param cursor cursor position in code - COMPLETION_LIST, // [s-c] send back completion candidates list - // @param id - // @param completions list of string + COMPLETION_LIST, // [s-c] send back completion candidates list + // @param id + // @param completions list of string - LIST_NOTES, // [c-s] ask list of note - RELOAD_NOTES_FROM_REPO, // [c-s] reload notes from repo + LIST_NOTES, // [c-s] ask list of note + RELOAD_NOTES_FROM_REPO, // [c-s] reload notes from repo - NOTES_INFO, // [s-c] list of note infos - // @param notes serialized List object + NOTES_INFO, // [s-c] list of note infos + // @param notes serialized List object PARAGRAPH_REMOVE, PARAGRAPH_CLEAR_OUTPUT, - PARAGRAPH_APPEND_OUTPUT, // [s-c] append output - PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output + PARAGRAPH_APPEND_OUTPUT, // [s-c] append output + PARAGRAPH_UPDATE_OUTPUT, // [s-c] update (replace) output PING, AUTH_INFO, - ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object - ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del + ANGULAR_OBJECT_UPDATE, // [s-c] add/update angular object + ANGULAR_OBJECT_REMOVE, // [s-c] add angular object del - ANGULAR_OBJECT_UPDATED, // [c-s] angular object value updated, + ANGULAR_OBJECT_UPDATED, // [c-s] angular object value updated, - ANGULAR_OBJECT_CLIENT_BIND, // [c-s] angular object updated from AngularJS z object + ANGULAR_OBJECT_CLIENT_BIND, // [c-s] angular object updated from AngularJS z object - ANGULAR_OBJECT_CLIENT_UNBIND, // [c-s] angular object unbind from AngularJS z object + ANGULAR_OBJECT_CLIENT_UNBIND, // [c-s] angular object unbind from AngularJS z object - LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations - CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations - // @param settings serialized Map object + LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations + CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations + // @param settings serialized Map object - CHECKPOINT_NOTEBOOK, // [c-s] checkpoint notebook to storage repository - // @param noteId - // @param checkpointName + CHECKPOINT_NOTEBOOK, // [c-s] checkpoint notebook to storage repository + // @param noteId + // @param checkpointName - APP_APPEND_OUTPUT, // [s-c] append output - APP_UPDATE_OUTPUT, // [s-c] update (replace) output - APP_LOAD, // [s-c] on app load - APP_STATUS_CHANGE, // [s-c] on app status change + APP_APPEND_OUTPUT, // [s-c] append output + APP_UPDATE_OUTPUT, // [s-c] update (replace) output + APP_LOAD, // [s-c] on app load + APP_STATUS_CHANGE, // [s-c] on app status change - LIST_NOTEBOOK_JOBS, // [c-s] get notebook job management infomations - LIST_UPDATE_NOTEBOOK_JOBS // [c-s] get job management informations for until unixtime - // @param unixTime + LIST_NOTEBOOK_JOBS, // [c-s] get notebook job management information + LIST_UPDATE_NOTEBOOK_JOBS // [c-s] get job management information for until unixtime + // @param unixTime } public OP op; diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java index cff66adc78d..2c2f4a56c6e 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteTest.java @@ -17,8 +17,6 @@ package org.apache.zeppelin.notebook; -import com.google.common.base.Optional; - import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterFactory; @@ -106,7 +104,7 @@ public void runJdbcTest() { @Test public void putDefaultReplNameIfInterpreterSettingAbsent() { when(interpreterFactory.getDefaultInterpreterSetting(anyString())) - .thenReturn(null); + .thenReturn(null); Note note = new Note(repo, interpreterFactory, jobListenerFactory, index, credentials, noteEventListener); note.putDefaultReplName(); diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java index bfa97e05de3..d2f73cb1c29 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java @@ -73,10 +73,15 @@ public void setUp() throws Exception { new File(tmpDir, "conf").mkdirs(); notebookDir = new File(tmpDir + "/notebook"); notebookDir.mkdirs(); + FileUtils.copyDirectory(new File("src/test/resources/interpreter"), new File(tmpDir, "interpreter")); + System.setProperty(ConfVars.ZEPPELIN_CONF_DIR.getVarName(), tmpDir.toString() + "/conf"); System.setProperty(ConfVars.ZEPPELIN_HOME.getVarName(), tmpDir.getAbsolutePath()); System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(), notebookDir.getAbsolutePath()); - System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1,org.apache.zeppelin.interpreter.mock.MockInterpreter2"); + System.setProperty(ConfVars.ZEPPELIN_INTERPRETERS.getVarName(), + "org.apache.zeppelin.interpreter.mock.MockInterpreter1," + + "org.apache.zeppelin.interpreter.mock.MockInterpreter2," + + "org.apache.zeppelin.interpreter.mock.MockInterpreter11"); conf = ZeppelinConfiguration.create(); @@ -94,8 +99,7 @@ public void setUp() throws Exception { credentials = new Credentials(conf.credentialsPersist(), conf.getCredentialsPath()); notebook = new Notebook(conf, notebookRepo, schedulerFactory, factory, this, search, - notebookAuthorization, credentials); - + notebookAuthorization, credentials); } @After @@ -825,6 +829,30 @@ else if(file.isDirectory()){ } } + @Test + public void getEditorSetting() throws IOException, RepositoryException, SchedulerException { + List intpIds = new ArrayList<>(); + for(InterpreterSetting intpSetting: factory.get()) { + if (intpSetting.getName().startsWith("mock1")) { + intpIds.add(intpSetting.id()); + } + } + Note note = notebook.createNote(intpIds, null); + + // get editor setting from interpreter-setting.json + Map editor = note.getEditorSetting("mock11"); + assertEquals("java", editor.get("language")); + + // when interpreter is not loaded via interpreter-setting.json + // or editor setting doesn't exit + editor = note.getEditorSetting("mock1"); + assertEquals(null, editor.get("language")); + + // when interpreter is not bound to note + editor = note.getEditorSetting("mock2"); + assertEquals("text", editor.get("language")); + } + @Override public ParagraphJobListener getParagraphJobListener(Note note) { return new ParagraphJobListener(){ diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java index 1f8519cb399..48814bb1da7 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java @@ -85,7 +85,7 @@ public void effectiveTextTest() { assertEquals("Get right replName", "jdbc", p.getRequiredReplName()); assertEquals("Get right scriptBody", "(h2) show databases", p.getScriptBody()); - when(interpreterFactory.getInterpreter(anyString(), eq("jdbc"))).thenReturn(interpreter); + when(note.getRepl(eq("jdbc"))).thenReturn(interpreter); when(interpreter.getFormType()).thenReturn(Interpreter.FormType.NATIVE); when(note.getId()).thenReturn("noteId"); diff --git a/zeppelin-zengine/src/test/resources/interpreter/mock/interpreter-setting.json b/zeppelin-zengine/src/test/resources/interpreter/mock/interpreter-setting.json new file mode 100644 index 00000000000..65568ef8a5c --- /dev/null +++ b/zeppelin-zengine/src/test/resources/interpreter/mock/interpreter-setting.json @@ -0,0 +1,12 @@ +[ + { + "group": "mock11", + "name": "mock11", + "className": "org.apache.zeppelin.interpreter.mock.MockInterpreter11", + "properties": { + }, + "editor": { + "language": "java" + } + } +]