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 '&nbsp;';
+      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();
   }
 
 

Reply via email to