You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2016/11/13 14:55:13 UTC

zeppelin git commit: [ZEPPELIN-1628] Enable renaming note from the main page

Repository: zeppelin
Updated Branches:
  refs/heads/master 5b1b81154 -> c1254f7b0


[ZEPPELIN-1628] Enable renaming note from the main page

### What is this PR for?
Now users can rename a note from the main page! This new feature will improve UX.

I divided [ZEPPELIN-1598](https://issues.apache.org/jira/browse/ZEPPELIN-1598) into sub-tasks since renaming folder gonna be huge. I will open PR for [ZEPPELIN-1629](https://issues.apache.org/jira/browse/ZEPPELIN-1629) after merging this PR.

By the way, I have a question! Does a `writer` can rename a note? Currently, only an owner can rename a note.

### What type of PR is it?
[Feature]

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-1628

### Screenshots (if appropriate)
![rename_note](https://cloud.githubusercontent.com/assets/8201019/20057051/a16b221c-a52c-11e6-900e-f88031ff1246.gif)

### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No

Author: Jun <i2...@gmail.com>

Closes #1609 from tae-jun/ZEPPELIN-1628 and squashes the following commits:

7d3f7bb [Jun] Fix factory/noteList.js test errors.
54bae60 [Jun] Add rename modal input validation
3eadcd8 [Jun] Add folder id
fb8b35f [Jun] Correct indent to pass style check
c019b9e [Jun] Rename a note from the main page


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/c1254f7b
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/c1254f7b
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/c1254f7b

Branch: refs/heads/master
Commit: c1254f7b0d29d311e7759af3054e5e5ac7c77c33
Parents: 5b1b811
Author: Jun <i2...@gmail.com>
Authored: Thu Nov 10 02:18:02 2016 +0900
Committer: Lee moon soo <mo...@apache.org>
Committed: Sun Nov 13 06:55:10 2016 -0800

----------------------------------------------------------------------
 .../apache/zeppelin/socket/NotebookServer.java  | 39 ++++++++++++---
 zeppelin-web/src/app/home/home.controller.js    |  4 ++
 zeppelin-web/src/app/home/home.html             | 14 ++++--
 .../components/noteAction/noteAction.service.js | 14 +++++-
 .../noteListDataFactory/noteList.datafactory.js |  4 +-
 .../src/components/rename/rename.controller.js  | 50 ++++++++++++++++++++
 zeppelin-web/src/components/rename/rename.html  | 42 ++++++++++++++++
 .../src/components/rename/rename.service.js     | 35 ++++++++++++++
 .../websocketEvents/websocketMsg.service.js     |  4 ++
 zeppelin-web/src/index.html                     |  5 ++
 zeppelin-web/test/spec/factory/noteList.js      |  8 ++--
 .../zeppelin/notebook/socket/Message.java       |  2 +
 12 files changed, 203 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/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 6cba536..8a84587 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
@@ -48,13 +48,7 @@ import org.apache.zeppelin.interpreter.InterpreterSetting;
 import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
 import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
-import org.apache.zeppelin.notebook.JobListenerFactory;
-import org.apache.zeppelin.notebook.Note;
-import org.apache.zeppelin.notebook.Notebook;
-import org.apache.zeppelin.notebook.NotebookAuthorization;
-import org.apache.zeppelin.notebook.NotebookEventListener;
-import org.apache.zeppelin.notebook.Paragraph;
-import org.apache.zeppelin.notebook.ParagraphJobListener;
+import org.apache.zeppelin.notebook.*;
 import org.apache.zeppelin.notebook.repo.NotebookRepo.Revision;
 import org.apache.zeppelin.notebook.socket.Message;
 import org.apache.zeppelin.notebook.socket.Message.OP;
@@ -234,6 +228,9 @@ public class NotebookServer extends WebSocketServlet implements
           case NOTE_UPDATE:
             updateNote(conn, userAndRoles, notebook, messagereceived);
             break;
+          case NOTE_RENAME:
+            renameNote(conn, userAndRoles, notebook, messagereceived);
+            break;
           case COMPLETION:
             completion(conn, userAndRoles, notebook, messagereceived);
             break;
@@ -701,6 +698,34 @@ public class NotebookServer extends WebSocketServlet implements
     }
   }
 
+  private void renameNote(NotebookSocket conn, HashSet<String> userAndRoles,
+                          Notebook notebook, Message fromMessage)
+      throws SchedulerException, IOException {
+    String noteId = (String) fromMessage.get("id");
+    String name = (String) fromMessage.get("name");
+
+    if (noteId == null) {
+      return;
+    }
+
+    NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
+    if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
+      permissionError(conn, "renameNote", fromMessage.principal,
+              userAndRoles, notebookAuthorization.getOwners(noteId));
+      return;
+    }
+
+    Note note = notebook.getNote(noteId);
+    if (note != null) {
+      note.setName(name);
+
+      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
+      note.persist(subject);
+      broadcastNote(note);
+      broadcastNoteList(subject, userAndRoles);
+    }
+  }
+
   private boolean isCronUpdated(Map<String, Object> configA,
       Map<String, Object> configB) {
     boolean cronUpdated = false;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/app/home/home.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.controller.js b/zeppelin-web/src/app/home/home.controller.js
index 1d11c79..839978f 100644
--- a/zeppelin-web/src/app/home/home.controller.js
+++ b/zeppelin-web/src/app/home/home.controller.js
@@ -88,6 +88,10 @@
       }
     });
 
+    $scope.renameNote = function(node) {
+      noteActionSrv.renameNote(node.id, node.path);
+    };
+
     $scope.removeNote = function(noteId) {
       noteActionSrv.removeNote(noteId, false);
     };

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/app/home/home.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.html b/zeppelin-web/src/app/home/home.html
index a8d5e56..0b9883e 100644
--- a/zeppelin-web/src/app/home/home.html
+++ b/zeppelin-web/src/app/home/home.html
@@ -14,20 +14,26 @@ limitations under the License.
 
 <script type="text/ng-template" id="notebook_folder_renderer.html">
   <div ng-if="node.children == null"
-       ng-mouseenter="showButton=true"
-       ng-mouseleave="showButton=false">
+       ng-mouseenter="showNoteButton=true"
+       ng-mouseleave="showNoteButton=false">
     <a style="text-decoration: none;" href="#/notebook/{{node.id}}">
       <i style="font-size: 10px;" class="icon-doc"/> {{noteName(node)}}
     </a>
     <a style="text-decoration: none;">
       <i style="font-size: 13px; margin-left: 10px; cursor: pointer; text-decoration: none;"
-         class="fa fa-eraser" ng-show="showButton" ng-click="clearAllParagraphOutput(node.id)"
+         class="fa fa-pencil" ng-show="showNoteButton" ng-click="renameNote(node)"
+         tooltip-placement="bottom" tooltip="Rename note">
+      </i>
+    </a>
+    <a style="text-decoration: none;">
+      <i style="font-size: 13px; margin-left: 2px; cursor: pointer; text-decoration: none;"
+         class="fa fa-eraser" ng-show="showNoteButton" ng-click="clearAllParagraphOutput(node.id)"
          tooltip-placement="bottom" tooltip="Clear output">
       </i>
     </a>
     <a style="text-decoration: none;">
       <i style="font-size: 13px; margin-left: 2px; cursor: pointer; text-decoration: none;"
-         class="fa fa-trash-o" ng-show="showButton" ng-click="removeNote(node.id)"
+         class="fa fa-trash-o" ng-show="showNoteButton" ng-click="removeNote(node.id)"
          tooltip-placement="bottom" tooltip="Remove note">
       </i>
     </a>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/components/noteAction/noteAction.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/noteAction/noteAction.service.js b/zeppelin-web/src/components/noteAction/noteAction.service.js
index 33e5722..3630fc8 100644
--- a/zeppelin-web/src/components/noteAction/noteAction.service.js
+++ b/zeppelin-web/src/components/noteAction/noteAction.service.js
@@ -16,9 +16,9 @@
 
   angular.module('zeppelinWebApp').service('noteActionSrv', noteActionSrv);
 
-  noteActionSrv.$inject = ['websocketMsgSrv', '$location'];
+  noteActionSrv.$inject = ['websocketMsgSrv', '$location', 'renameSrv'];
 
-  function noteActionSrv(websocketMsgSrv, $location) {
+  function noteActionSrv(websocketMsgSrv, $location, renameSrv) {
     this.removeNote = function(noteId, redirectToHome) {
       BootstrapDialog.confirm({
         closable: true,
@@ -47,5 +47,15 @@
         }
       });
     };
+
+    this.renameNote = function(noteId, notePath) {
+      renameSrv.openRenameModal({
+        title: 'Rename note',
+        oldName: notePath,
+        callback: function(newName) {
+          websocketMsgSrv.renameNote(noteId, newName);
+        }
+      });
+    };
   }
 })();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/components/noteListDataFactory/noteList.datafactory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/noteListDataFactory/noteList.datafactory.js b/zeppelin-web/src/components/noteListDataFactory/noteList.datafactory.js
index 24ddca7..0f700b6 100644
--- a/zeppelin-web/src/components/noteListDataFactory/noteList.datafactory.js
+++ b/zeppelin-web/src/components/noteListDataFactory/noteList.datafactory.js
@@ -43,7 +43,8 @@
       if (nodes.length === 1) {  // the leaf
         curDir.children.push({
           name: nodes[0],
-          id: noteId
+          id: noteId,
+          path: curDir.id ? curDir.id + '/' + nodes[0] : nodes[0]
         });
       } else {  // a folder node
         var node = nodes.shift();
@@ -53,6 +54,7 @@
           addNode(dir, nodes, noteId);
         } else {
           var newDir = {
+            id: curDir.id ? curDir.id + '/' + node : node,
             name: node,
             hidden: true,
             children: []

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/components/rename/rename.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/rename/rename.controller.js b/zeppelin-web/src/components/rename/rename.controller.js
new file mode 100644
index 0000000..ad27bec
--- /dev/null
+++ b/zeppelin-web/src/components/rename/rename.controller.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ */
+'use strict';
+(function() {
+
+  angular.module('zeppelinWebApp').controller('RenameCtrl', RenameCtrl);
+
+  RenameCtrl.$inject = ['$scope'];
+
+  function RenameCtrl($scope) {
+    var self = this;
+
+    $scope.params = {newName: ''};
+    $scope.isValid = true;
+
+    $scope.rename = function() {
+      angular.element('#renameModal').modal('hide');
+      self.callback($scope.params.newName);
+    };
+
+    $scope.$on('openRenameModal', function(event, options) {
+      self.validator = options.validator || defaultValidator;
+      self.callback = options.callback || function() {};
+
+      $scope.title = options.title || 'Rename';
+      $scope.params.newName = options.oldName || '';
+      $scope.validate = function() {
+        $scope.isValid = self.validator($scope.params.newName);
+      };
+
+      angular.element('#renameModal').modal('show');
+    });
+
+    function defaultValidator(str) {
+      return !!str.trim();
+    }
+  }
+
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/components/rename/rename.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/rename/rename.html b/zeppelin-web/src/components/rename/rename.html
new file mode 100644
index 0000000..723c6aa
--- /dev/null
+++ b/zeppelin-web/src/components/rename/rename.html
@@ -0,0 +1,42 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 id="renameModal" class="modal fade" role="dialog"
+     tabindex="-1">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal">&times;</button>
+        <h4 class="modal-title">{{title}}</h4>
+      </div>
+      <div class="modal-body">
+        <label ng-if="isValid">Please enter a new name</label>
+          <label class="text-danger" ng-if="!isValid">Please enter a valid name</label>
+        <div class="form-group" ng-class="{'has-error': !isValid}">
+          <input type="text" class="form-control"
+            ng-model="params.newName" ng-change="validate()"
+            ng-keyup="$event.keyCode == 13 && isValid && rename()" />
+        </div>
+
+      </div>
+      <div class="modal-footer">
+        <div>
+          <button type="button" class="btn btn-default btn-primary"
+            ng-click="rename()" ng-class="{'disabled': !isValid}">
+            Rename
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/components/rename/rename.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/rename/rename.service.js b/zeppelin-web/src/components/rename/rename.service.js
new file mode 100644
index 0000000..1e19b24
--- /dev/null
+++ b/zeppelin-web/src/components/rename/rename.service.js
@@ -0,0 +1,35 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ */
+'use strict';
+(function() {
+
+  angular.module('zeppelinWebApp').service('renameSrv', renameSrv);
+
+  renameSrv.$inject = ['$rootScope'];
+
+  function renameSrv($rootScope) {
+    var self = this;
+
+    /**
+     * <options schema>
+     * title: string - Modal title
+     * oldName: string - name to initialize input
+     * callback: (newName: string)=>void - callback onButtonClick
+     * validator: (str: string)=>boolean - input validator
+     */
+    self.openRenameModal = function(options) {
+      $rootScope.$broadcast('openRenameModal', options);
+    };
+  }
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/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 8c025cc..5ffdaf7 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -59,6 +59,10 @@
         websocketEvents.sendNewEvent({op: 'NOTE_UPDATE', data: {id: noteId, name: noteName, config: noteConfig}});
       },
 
+      renameNote: function(noteId, noteName) {
+        websocketEvents.sendNewEvent({op: 'NOTE_RENAME', data: {id: noteId, name: noteName}});
+      },
+
       moveParagraph: function(paragraphId, newIndex) {
         websocketEvents.sendNewEvent({op: 'MOVE_PARAGRAPH', data: {id: paragraphId, index: newIndex}});
       },

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 3208860..fc000a9 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -93,6 +93,9 @@ limitations under the License.
     <div ng-controller="LoginCtrl as noteimportctrl">
       <div id="login-container" ng-include src="'components/login/login.html'"></div>
     </div>
+    <div ng-controller="RenameCtrl">
+      <div ng-include src="'components/rename/rename.html'"></div>
+    </div>
     <!-- build:js(.) scripts/oldieshim.js -->
     <!--[if lt IE 9]>
     <script src="bower_components/es5-shim/es5-shim.js"></script>
@@ -212,6 +215,8 @@ limitations under the License.
     <script src="components/login/login.controller.js"></script>
     <script src="components/elasticInputCtrl/elasticInput.controller.js"></script>
     <script src="components/noteAction/noteAction.service.js"></script>
+    <script src="components/rename/rename.controller.js"></script>
+    <script src="components/rename/rename.service.js"></script>
     <!-- endbuild -->
   </body>
 </html>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-web/test/spec/factory/noteList.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/test/spec/factory/noteList.js b/zeppelin-web/test/spec/factory/noteList.js
index 07eac9c..e1c9a76 100644
--- a/zeppelin-web/test/spec/factory/noteList.js
+++ b/zeppelin-web/test/spec/factory/noteList.js
@@ -46,7 +46,7 @@ describe('Factory: NoteList', function() {
     expect(folderList[1].name).toBe('B');
     expect(folderList[2].name).toBe('000003');
     expect(folderList[3].name).toBe('C');
-    expect(folderList[3].id).toBeUndefined();
+    expect(folderList[3].id).toBe('C');
     expect(folderList[3].children.length).toBe(3);
     expect(folderList[3].children[0].name).toBe('CA');
     expect(folderList[3].children[0].id).toBe('000004');
@@ -55,7 +55,7 @@ describe('Factory: NoteList', function() {
     expect(folderList[3].children[1].id).toBe('000005');
     expect(folderList[3].children[1].children).toBeUndefined();
     expect(folderList[3].children[2].name).toBe('CB');
-    expect(folderList[3].children[2].id).toBeUndefined();
+    expect(folderList[3].children[2].id).toBe('C/CB');
     expect(folderList[3].children[2].children.length).toBe(3);
     expect(folderList[3].children[2].children[0].name).toBe('CBA');
     expect(folderList[3].children[2].children[0].id).toBe('000006');
@@ -67,10 +67,10 @@ describe('Factory: NoteList', function() {
     expect(folderList[3].children[2].children[2].id).toBe('000008');
     expect(folderList[3].children[2].children[2].children).toBeUndefined();
     expect(folderList[4].name).toBe('D');
-    expect(folderList[4].id).toBeUndefined();
+    expect(folderList[4].id).toBe('D');
     expect(folderList[4].children.length).toBe(1);
     expect(folderList[4].children[0].name).toBe('D[A');
-    expect(folderList[4].children[0].id).toBeUndefined();
+    expect(folderList[4].children[0].id).toBe('D/D[A');
     expect(folderList[4].children[0].children[0].name).toBe('DA]B');
     expect(folderList[4].children[0].children[0].id).toBe('000009');
     expect(folderList[4].children[0].children[0].children).toBeUndefined();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/c1254f7b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
----------------------------------------------------------------------
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 b4da1e1..9fe9636 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
@@ -53,6 +53,8 @@ public class Message {
                       // @param object notebook
     NOTE_UPDATE,
 
+    NOTE_RENAME,
+
     RUN_PARAGRAPH,    // [c-s] run paragraph
                       // @param id paragraph id
                       // @param paragraph paragraph content.ie. script