Repository: incubator-zeppelin Updated Branches: refs/heads/master 6d42af171 -> ee8f64b27
ZEPPELIN-101, add auto-save paragraph feature this pull request add save button to paragraph. related JIra issue is https://issues.apache.org/jira/browse/ZEPPELIN-101 Author: Darren ha <[email protected]> Author: Darren Ha <[email protected]> Closes #168 from nberserk/master and squashes the following commits: bd0ff7a [Darren Ha] var name changed; do not append outdated for never run 5201c15 [Darren ha] Merge branch 'upstream' 593d119 [Darren Ha] Merge remote-tracking branch 'upstream/master' 7edbef8 [Darren Ha] Merge remote-tracking branch 'upstream/master' 878d43e [Darren ha] less aggressive color for dirty class 66cc991 [Darren ha] show paragraph's dirty state by reddish vertical bar d6f6e27 [Darren ha] fix outdated logic; show save button always c05bc25 [Darren ha] move save button from paragraph to notebook c2d714e [Darren ha] Merging 'upstream/master' 31d143f [Darren ha] Revert "disable move up/down when unsaved contents to address ZEPPELIN-138" 56747b1 [Darren ha] disable move up/down when unsaved contents to address ZEPPELIN-136 4a62305 [Darren ha] change icon to floppy-save of awesomefont 71b07f2 [Darren Ha] fix test failure. move icon to the right 250951b [Darren Ha] Merge branch 'master' of https://github.com/nberserk/incubator-zeppelin merge saveParagraph automatically 3e85988 [Darren ha] add status msg outdated 9210280 [Darren Ha] adding save timer 78f0168 [Darren ha] recover prev indentation 83a8aef [Darren ha] add save button to paragraph toolbar b38118c [Darren ha] add status msg outdated c9cd2e7 [Darren Ha] adding save timer 30b15a1 [Darren ha] recover prev indentation cbebc8e [Darren ha] add save button to paragraph toolbar Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/ee8f64b2 Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/ee8f64b2 Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/ee8f64b2 Branch: refs/heads/master Commit: ee8f64b2728d9de746172f30e924aa952c3fdafa Parents: 6d42af1 Author: Darren ha <[email protected]> Authored: Wed Aug 12 16:07:27 2015 +0900 Committer: Lee moon soo <[email protected]> Committed: Thu Aug 13 08:36:16 2015 -0700 ---------------------------------------------------------------------- .../apache/zeppelin/socket/NotebookServer.java | 1 - .../src/app/notebook/notebook.controller.js | 29 +++++++++++++-- zeppelin-web/src/app/notebook/notebook.css | 6 +++- zeppelin-web/src/app/notebook/notebook.html | 2 +- .../notebook/paragraph/paragraph.controller.js | 37 ++++++++++++++++---- .../src/app/notebook/paragraph/paragraph.html | 7 ++-- zeppelin-web/test/karma.conf.js | 2 +- .../org/apache/zeppelin/notebook/Paragraph.java | 19 ++++------ 8 files changed, 76 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java ---------------------------------------------------------------------- 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 fe0d391..07265d5 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 @@ -560,7 +560,6 @@ public class NotebookServer extends WebSocketServlet implements } note.persist(); broadcastNote(note); - try { note.run(paragraphId); } catch (Exception ex) { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/src/app/notebook/notebook.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 32ff305..0eb4b77 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -15,7 +15,7 @@ */ 'use strict'; -angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, $rootScope, $http, websocketMsgSrv, baseUrlSrv) { +angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, $rootScope, $http, websocketMsgSrv, baseUrlSrv, $timeout) { $scope.note = null; $scope.showEditor = false; $scope.editorToggled = false; @@ -35,6 +35,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro $scope.interpreterSettings = []; $scope.interpreterBindings = []; + $scope.isNoteDirty = null; + $scope.saveTimer = null; var angularObjectRegistry = {}; @@ -77,6 +79,13 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } }; + $scope.saveNote = function() { + _.forEach($scope.note.paragraphs, function(n, key) { + angular.element('#' + n.id + '_paragraphColumn_main').scope().saveParagraph(); + }); + $scope.isNoteDirty = null; + }; + $scope.toggleAllEditor = function() { if ($scope.editorToggled) { $scope.$broadcast('openEditor'); @@ -123,8 +132,24 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro return running; }; + $scope.killSaveTimer = function() { + if($scope.saveTimer){ + $timeout.cancel($scope.saveTimer); + $scope.saveTimer = null; + } + }; + + $scope.startSaveTimer = function() { + $scope.killSaveTimer(); + $scope.isNoteDirty = true; + console.log('startSaveTimer called ' + $scope.note.id); + $scope.saveTimer = $timeout(function(){ + $scope.saveNote(); + }, 10000); + }; + $scope.setLookAndFeel = function(looknfeel) { - $scope.note.config.looknfeel = looknfeel; + $scope.note.config.looknfeel = looknfeel; $scope.setConfig(); }; http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/src/app/notebook/notebook.css ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css index 477e7a1..0d3de89 100644 --- a/zeppelin-web/src/app/notebook/notebook.css +++ b/zeppelin-web/src/app/notebook/notebook.css @@ -182,11 +182,15 @@ .paragraph .editor { width: 100%; - border-left: 4px solid #DDDDDD; + border-left: 4px solid #DDDDDD; background: rgba(255, 255, 255, 0.0); margin: 7px 0 2px 0px; } +.dirty{ + border-left: 4px solid #E67E22 !important; +} + .paragraph .text { white-space: pre; display: block; http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/src/app/notebook/notebook.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html index d09744e..d618057 100644 --- a/zeppelin-web/src/app/notebook/notebook.html +++ b/zeppelin-web/src/app/notebook/notebook.html @@ -25,7 +25,7 @@ limitations under the License. ng-if="!isNoteRunning()" tooltip-placement="top" tooltip="Run all the notes"> <i class="icon-control-play"></i> - </button> + </button> <button type="button" class="btn btn-default btn-xs" http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 217ea1b..56d3a8f 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -21,6 +21,7 @@ angular.module('zeppelinWebApp') $scope.paragraph = null; $scope.editor = null; + var editorMode = {scala: 'ace/mode/scala', sql: 'ace/mode/sql', markdown: 'ace/mode/markdown'}; // Controller init @@ -155,6 +156,7 @@ angular.module('zeppelinWebApp') data.paragraph.dateCreated !== $scope.paragraph.dateCreated || data.paragraph.dateFinished !== $scope.paragraph.dateFinished || data.paragraph.dateStarted !== $scope.paragraph.dateStarted || + data.paragraph.dateUpdated !== $scope.paragraph.dateUpdated || data.paragraph.status !== $scope.paragraph.status || data.paragraph.jobName !== $scope.paragraph.jobName || data.paragraph.title !== $scope.paragraph.title || @@ -190,6 +192,7 @@ angular.module('zeppelinWebApp') /** push the rest */ $scope.paragraph.aborted = data.paragraph.aborted; + $scope.paragraph.dateUpdated = data.paragraph.dateUpdated; $scope.paragraph.dateCreated = data.paragraph.dateCreated; $scope.paragraph.dateFinished = data.paragraph.dateFinished; $scope.paragraph.dateStarted = data.paragraph.dateStarted; @@ -243,13 +246,20 @@ angular.module('zeppelinWebApp') websocketMsgSrv.cancelParagraphRun($scope.paragraph.id); }; - $scope.runParagraph = function(data) { websocketMsgSrv.runParagraph($scope.paragraph.id, $scope.paragraph.title, data, $scope.paragraph.config, $scope.paragraph.settings.params); $scope.dirtyText = undefined; }; + $scope.saveParagraph = function(){ + if($scope.dirtyText === undefined){ + return; + } + commitParagraph($scope.paragraph.title, $scope.dirtyText, $scope.paragraph.config, $scope.paragraph.settings.params); + $scope.dirtyText = undefined; + }; + $scope.moveUp = function() { $scope.$emit('moveParagraphUp', $scope.paragraph.id); }; @@ -410,6 +420,7 @@ angular.module('zeppelinWebApp') $scope.aceChanged = function() { $scope.dirtyText = $scope.editor.getSession().getValue(); + $scope.startSaveTimer(); }; $scope.aceLoaded = function(_editor) { @@ -457,7 +468,6 @@ angular.module('zeppelinWebApp') // ensure the correct mode is set $scope.setParagraphMode(session, buf); - websocketMsgSrv.completion($scope.paragraph.id, buf, pos); $scope.$on('completionList', function(event, data) { @@ -476,7 +486,7 @@ angular.module('zeppelinWebApp') }); } }; - + langTools.setCompleters([remoteCompleter, langTools.keyWordCompleter, langTools.snippetCompleter, langTools.textCompleter]); $scope.editor.setOptions({ @@ -577,15 +587,30 @@ angular.module('zeppelinWebApp') $scope.getProgress = function() { return ($scope.currentProgress) ? $scope.currentProgress : 0; - }; + }; $scope.getExecutionTime = function() { var pdata = $scope.paragraph; var timeMs = Date.parse(pdata.dateFinished) - Date.parse(pdata.dateStarted); if (isNaN(timeMs) || timeMs < 0) { - return ' '; + if ($scope.isResultOutdated()){ + return 'outdated'; + } + return ''; + } + var desc = 'Took ' + (timeMs/1000) + ' seconds.'; + if ($scope.isResultOutdated()){ + desc += ' (outdated)'; + } + return desc; + }; + + $scope.isResultOutdated = function() { + var pdata = $scope.paragraph; + if (pdata.dateUpdated !==undefined && Date.parse(pdata.dateUpdated) > Date.parse(pdata.dateStarted)){ + return true; } - return 'Took ' + (timeMs/1000) + ' seconds'; + return false; }; $scope.$on('updateProgress', function(event, data) { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/src/app/notebook/paragraph/paragraph.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.html b/zeppelin-web/src/app/notebook/paragraph/paragraph.html index 321da26..4d0f451 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.html @@ -41,7 +41,7 @@ limitations under the License. require : ['ace/ext/language_tools'] }" ng-model="paragraph.text" - ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"> + ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING', dirty : dirtyText}"> </div> </div> @@ -392,12 +392,13 @@ limitations under the License. <span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C" tooltip-placement="top" tooltip="Cancel" ng-click="cancelParagraph()" ng-show="paragraph.status=='RUNNING' || paragraph.status=='PENDING'"></span> - <span class="{{paragraph.config.editorHide ? 'icon-size-fullscreen' : 'icon-size-actual'}}" style="cursor:pointer;" tooltip-placement="top" tooltip="{{(paragraph.config.editorHide ? 'Show' : 'Hide') + ' editor'}}" ng-click="toggleEditor()"></span> <span class="{{paragraph.config.tableHide ? 'icon-notebook' : 'icon-book-open'}}" style="cursor:pointer;" tooltip-placement="top" tooltip="{{(paragraph.config.tableHide ? 'Show' : 'Hide') + ' output'}}" ng-click="toggleOutput()"></span> - + <span style="cursor:pointer;" + ng-click="saveParagraph()" + ng-show="dirtyText"></span> <span class="dropdown navbar-right"> <span class="icon-settings" style="cursor:pointer" data-toggle="dropdown" http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-web/test/karma.conf.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js index e23a186..e85abdb 100644 --- a/zeppelin-web/test/karma.conf.js +++ b/zeppelin-web/test/karma.conf.js @@ -51,7 +51,7 @@ module.exports = function(config) { 'bower_components/angular-xeditable/dist/js/xeditable.js', 'bower_components/highlightjs/highlight.pack.js', 'bower_components/lodash/lodash.js', - 'bower_components/angular-filter/dist/angular-filter.js', + 'bower_components/angular-filter/dist/angular-filter.min.js', 'bower_components/angular-mocks/angular-mocks.js', // endbower 'src/app/app.js', http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/ee8f64b2/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java ---------------------------------------------------------------------- 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 5a43198..50756e8 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 @@ -17,27 +17,19 @@ package org.apache.zeppelin.notebook; -import java.io.Serializable; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; - import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.GUI; import org.apache.zeppelin.display.Input; -import org.apache.zeppelin.interpreter.Interpreter; +import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.Interpreter.FormType; -import org.apache.zeppelin.interpreter.InterpreterContext; -import org.apache.zeppelin.interpreter.InterpreterContextRunner; -import org.apache.zeppelin.interpreter.InterpreterResult; -import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.JobListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.*; + /** * Paragraph is a representation of an execution unit. * @@ -50,6 +42,7 @@ public class Paragraph extends Job implements Serializable { String title; String text; + Date dateUpdated; private Map<String, Object> config; // paragraph configs like isOpen, colWidth, etc public final GUI settings; // form and parameter settings @@ -59,6 +52,7 @@ public class Paragraph extends Job implements Serializable { this.replLoader = replLoader; title = null; text = null; + dateUpdated = null; settings = new GUI(); config = new HashMap<String, Object>(); } @@ -74,6 +68,7 @@ public class Paragraph extends Job implements Serializable { public void setText(String newText) { this.text = newText; + this.dateUpdated = new Date(); }
