Repository: incubator-zeppelin Updated Branches: refs/heads/master 4818f07ad -> 7f7a7c41a
Clone a note Adding ability to clone a note. New button on top of note to clone it  Dialog asking for a name for the new note(clone)  The cloned note  Author: Karuppayya <[email protected]> Author: Karuppayya <[email protected]> Closes #219 from Karuppayya/clonenote and squashes the following commits: 5c560a9 [Karuppayya] merge master de4c132 [Karuppayya] Incorporate review comments 39a0919 [Karuppayya] Minor bug fixes 12a9e45 [Karuppayya] Minor bug fixes 4f1f434 [Karuppayya] implement clone method in paragraph 39c0f46 [Karuppayya] Ability to clone a note Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/7f7a7c41 Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/7f7a7c41 Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/7f7a7c41 Branch: refs/heads/master Commit: 7f7a7c41af7df8fa784c7c83d581d0c1c1724d67 Parents: 4818f07 Author: Karuppayya <[email protected]> Authored: Wed Aug 19 10:28:32 2015 +0530 Committer: Lee moon soo <[email protected]> Committed: Wed Aug 19 21:22:00 2015 -0700 ---------------------------------------------------------------------- .../org/apache/zeppelin/socket/Message.java | 3 ++ .../apache/zeppelin/socket/NotebookServer.java | 31 +++++++++++++++++++- .../src/app/notebook/notebook.controller.js | 9 ++++++ zeppelin-web/src/app/notebook/notebook.html | 7 +++++ .../noteName-create/note-name-dialog.html | 15 ++++++---- .../noteName-create/notename.controller.js | 16 ++++++---- .../noteName-create/visible.directive.js | 5 +++- .../websocketEvents/websocketMsg.service.js | 3 ++ zeppelin-web/src/index.html | 4 ++- .../java/org/apache/zeppelin/notebook/Note.java | 11 +++++++ .../org/apache/zeppelin/notebook/Paragraph.java | 21 +++++++++++-- 11 files changed, 110 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java index 1784c53..f2b34aa 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java @@ -52,6 +52,9 @@ public class Message { 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 NOTE_UPDATE, RUN_PARAGRAPH, // [c-s] run paragraph http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/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 8a02b53..5467fe6 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 @@ -24,6 +24,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; + import javax.servlet.http.HttpServletRequest; import org.apache.zeppelin.conf.ZeppelinConfiguration; @@ -38,8 +39,8 @@ import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.scheduler.Job; -import org.apache.zeppelin.scheduler.JobListener; import org.apache.zeppelin.scheduler.Job.Status; +import org.apache.zeppelin.scheduler.JobListener; import org.apache.zeppelin.server.ZeppelinServer; import org.apache.zeppelin.socket.Message.OP; import org.eclipse.jetty.websocket.WebSocket; @@ -47,6 +48,7 @@ import org.eclipse.jetty.websocket.WebSocketServlet; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import com.google.common.base.Strings; import com.google.gson.Gson; /** @@ -125,6 +127,9 @@ public class NotebookServer extends WebSocketServlet implements case DEL_NOTE: removeNote(conn, notebook, messagereceived); break; + case CLONE_NOTE: + cloneNote(conn, notebook, messagereceived); + break; case COMMIT_PARAGRAPH: updateParagraph(conn, notebook, messagereceived); break; @@ -429,6 +434,30 @@ public class NotebookServer extends WebSocketServlet implements note.persist(); broadcast(note.id(), new Message(OP.PARAGRAPH).put("paragraph", p)); } + + private void cloneNote(NotebookSocket conn, Notebook notebook, Message fromMessage) + throws IOException, CloneNotSupportedException { + String noteId = getOpenNoteId(conn); + String name = (String) fromMessage.get("name"); + Note sourceNote = notebook.getNote(noteId); + Note newNote = notebook.createNote(); + if (name != null) { + newNote.setName(name); + } + // Copy the interpreter bindings + List<String> boundInterpreterSettingsIds = notebook + .getBindedInterpreterSettingsIds(sourceNote.id()); + notebook.bindInterpretersToNote(newNote.id(), boundInterpreterSettingsIds); + + List<Paragraph> paragraphs = sourceNote.getParagraphs(); + for (Paragraph para : paragraphs) { + Paragraph p = (Paragraph) para.clone(); + newNote.addParagraph(p); + } + newNote.persist(); + broadcastNote(newNote); + broadcastNoteList(); + } private void removeParagraph(NotebookSocket conn, Notebook notebook, Message fromMessage) throws IOException { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/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 0eb4b77..07d8325 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -70,6 +70,15 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } }; + //Clone note + $scope.cloneNote = function(noteId) { + var result = confirm('Do you want to clone this notebook?'); + if (result) { + websocketMsgSrv.cloneNotebook(noteId); + $location.path('/#'); + } + }; + $scope.runNote = function() { var result = confirm('Run all paragraphs?'); if (result) { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/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 d618057..5079685 100644 --- a/zeppelin-web/src/app/notebook/notebook.html +++ b/zeppelin-web/src/app/notebook/notebook.html @@ -47,6 +47,13 @@ limitations under the License. ng-hide="viewOnly" tooltip-placement="top" tooltip="Remove the notebook"> <i class="icon-trash"></i></button> + <button type="button" + class="btn btn-default btn-xs" + ng-hide="viewOnly" + tooltip-placement="top" tooltip="Clone the notebook" + data-toggle="modal" data-target="#noteNameModal" data-clone="true" + > + <i class="fa fa-copy"></i></button> </span> <span ng-hide="viewOnly"> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-web/src/components/noteName-create/note-name-dialog.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/noteName-create/note-name-dialog.html b/zeppelin-web/src/components/noteName-create/note-name-dialog.html index e7670f9..d1bcd49 100644 --- a/zeppelin-web/src/components/noteName-create/note-name-dialog.html +++ b/zeppelin-web/src/components/noteName-create/note-name-dialog.html @@ -11,8 +11,7 @@ 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. --> -<div ng-controller="NotenameCtrl as notenamectrl"> - <div id="noteNameModal" class="modal fade" role="dialog" modalvisible previsiblecallback="notenamectrl.preVisible()" + <div id="noteNameModal" class="modal fade" role="dialog" modalvisible previsiblecallback="notenamectrl.preVisible" targetinput="noteName" tabindex='-1'> <div class="modal-dialog"> @@ -20,22 +19,28 @@ limitations under the License. <div class="modal-content" id="NotenameCtrl"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal">×</button> - <h4 class="modal-title">Create new note</h4> + <h4 class="modal-title" ng-show="!notenamectrl.clone">Create new note</h4> + <h4 class="modal-title" ng-show="notenamectrl.clone">Clone note</h4> </div> <div class="modal-body"> <div class="form-group"> <label for="noteName">Note Name</label> <input placeholder="Note name" type="text" class="form-control" - id="noteName" ng-model="notename"> + id="noteName" ng-model="note.notename"> </div> </div> <div class="modal-footer"> <button type="button" id="createNoteButton" class="btn btn-default" + ng-show="!notenamectrl.clone" data-dismiss="modal" ng-click="notenamectrl.createNote()">Create Note</button> + <button type="button" id="createNoteButton" + class="btn btn-default" + ng-show="notenamectrl.clone" + data-dismiss="modal" ng-click="notenamectrl.createNote()">Clone + Note</button> </div> </div> </div> </div> -</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-web/src/components/noteName-create/notename.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/noteName-create/notename.controller.js b/zeppelin-web/src/components/noteName-create/notename.controller.js index 3f0e82d..342cad7 100644 --- a/zeppelin-web/src/components/noteName-create/notename.controller.js +++ b/zeppelin-web/src/components/noteName-create/notename.controller.js @@ -14,16 +14,22 @@ 'use strict'; -angular.module('zeppelinWebApp').controller('NotenameCtrl', function($scope, $rootScope, websocketMsgSrv) { +angular.module('zeppelinWebApp').controller('NotenameCtrl', function($scope, $rootScope, $routeParams, websocketMsgSrv) { var vm = this; vm.websocketMsgSrv = websocketMsgSrv; - + $scope.note = {}; vm.createNote = function(){ - vm.websocketMsgSrv.createNotebook($scope.notename); + if(!vm.clone){ + vm.websocketMsgSrv.createNotebook($scope.note.notename); + }else{ + var noteId = $routeParams.noteId; + vm.websocketMsgSrv.cloneNotebook(noteId, $scope.note.notename); + } }; - vm.preVisible = function(){ + vm.preVisible = function(clone){ var generatedName = vm.generateName(); - $scope.notename = 'Note ' + generatedName; + $scope.note.notename = 'Note ' + generatedName; + vm.clone = clone; $scope.$apply(); }; vm.generateName = function () { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-web/src/components/noteName-create/visible.directive.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/noteName-create/visible.directive.js b/zeppelin-web/src/components/noteName-create/visible.directive.js index de3fc35..3c15dcb 100644 --- a/zeppelin-web/src/components/noteName-create/visible.directive.js +++ b/zeppelin-web/src/components/noteName-create/visible.directive.js @@ -26,7 +26,10 @@ angular.module('zeppelinWebApp').directive('modalvisible', function () { var previsibleMethod = scope.preVisibleCallback; var postVisibleMethod = scope.postVisibleCallback; elem.on('show.bs.modal',function(e) { - previsibleMethod(); + var relatedTgt = angular.element(e.relatedTarget); + var clone = relatedTgt.data('clone'); + var cloneNote = clone ? true : false; + previsibleMethod()(cloneNote); }); elem.on('shown.bs.modal', function(e) { if(scope.targetinput) { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js index a30905c..e14c17a 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js +++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js @@ -29,6 +29,9 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, websocketEvents.sendNewEvent({op: 'DEL_NOTE', data: {id: noteId}}); }, + cloneNotebook: function(noteIdToClone, newNoteName ) { + websocketEvents.sendNewEvent({op: 'CLONE_NOTE', data: {id: noteIdToClone, name: newNoteName}}); + }, getNotebookList: function() { websocketEvents.sendNewEvent({op: 'LIST_NOTES'}); }, http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-web/src/index.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 6fdc2f2..90b3f0a 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -67,7 +67,9 @@ limitations under the License. </div> <!-- Modal :: Keyboard shortcuts --> <div ng-include src="'components/modal-shortcut/modal-shortcut.html'"></div> - <div id="note-modal-container" ng-include src="'components/noteName-create/note-name-dialog.html'"></div> + <div ng-controller="NotenameCtrl as notenamectrl"> + <div id="note-modal-container" ng-include src="'components/noteName-create/note-name-dialog.html'"></div> + </div> <!-- build:js(.) scripts/oldieshim.js --> <!--[if lt IE 9]> <script src="bower_components/es5-shim/es5-shim.js"></script> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java ---------------------------------------------------------------------- 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 20cfa96..ac267fb 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 @@ -140,6 +140,17 @@ public class Note implements Serializable, JobListener { } /** + * Add the paragraph p to the list of paras in note. + * + * @param p + */ + public void addParagraph(Paragraph p) { + synchronized (paragraphs) { + paragraphs.add(p); + } + } + + /** * Insert paragraph in given index. * * @param index http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7f7a7c41/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 50756e8..33a427c 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 @@ -35,7 +35,7 @@ import java.util.*; * * @author Leemoonsoo */ -public class Paragraph extends Job implements Serializable { +public class Paragraph extends Job implements Serializable, Cloneable { private static final transient long serialVersionUID = -6328572073497992016L; private transient NoteInterpreterLoader replLoader; private transient Note note; @@ -272,6 +272,23 @@ public class Paragraph extends Job implements Serializable { public void setReturn(InterpreterResult value, Throwable t) { setResult(value); setException(t); - + } + + @Override + public Object clone() throws CloneNotSupportedException { + Paragraph paraClone = (Paragraph) super.clone(); + Map<String, Object> config = new HashMap<>(this.getConfig()); + // Show the editor by default + String hideEditorKey = "editorHide"; + Object object = config.get(hideEditorKey); + if (object != null && object == Boolean.TRUE) { + config.put(hideEditorKey, Boolean.FALSE); + } + Map<String, Object> param = new HashMap<>(this.settings.getParams()); + paraClone.setConfig(config); + paraClone.settings.setParams(param); + paraClone.setTitle(this.getTitle()); + paraClone.setText(this.getText()); + return paraClone; } }
