You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by zj...@apache.org on 2021/04/26 02:32:17 UTC
[zeppelin] branch branch-0.9 updated: [ZEPPELIN-4506] Check for
duplicate note on rename of a notebook
This is an automated email from the ASF dual-hosted git repository.
zjffdu pushed a commit to branch branch-0.9
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/branch-0.9 by this push:
new 01ce002 [ZEPPELIN-4506] Check for duplicate note on rename of a notebook
01ce002 is described below
commit 01ce002040c646ca5a9b18782923d426b5dbb48b
Author: Danny Cranmer <da...@apache.org>
AuthorDate: Tue Apr 13 13:19:07 2021 +0100
[ZEPPELIN-4506] Check for duplicate note on rename of a notebook
### What is this PR for?
When a user renames a note Zeppelin does not check for existing notes with the same name/path. This can result in errors as multiple notes can have conflicting paths.
### What type of PR is it?
Bug Fix
### Todos
* [x] - Add validation to Java code
* [x] - Add java unit tests
### What is the Jira issue?
* [ZEPPELIN-4506](https://issues.apache.org/jira/browse/ZEPPELIN-4506)
### How should this be tested?
* Java unit tests
* Manual test
### Screenshots (if appropriate)
![test__-_Zeppelin](https://user-images.githubusercontent.com/4950503/114375043-30171080-9b7c-11eb-81fe-0869a5a395dc.png)
### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: Danny Cranmer <da...@apache.org>
Closes #4092 from dannycranmer/ZEPPELIN-4506 and squashes the following commits:
02701c784 [Danny Cranmer] [ZEPPELIN-4506] Renaming validation method inline with PR feedback
a7467d52f [Danny Cranmer] [ZEPPELIN-4506] Updates after PR feedback. Removing web changes in favour of server side only change
e264a6df0 [Danny Cranmer] [ZEPPELIN-4506] Check for duplicate note on rename of a notebook
(cherry picked from commit 164e517bea65c0b145967afc159cf9971bc9a63e)
Signed-off-by: Jeff Zhang <zj...@apache.org>
---
.../apache/zeppelin/service/NotebookService.java | 9 ++++--
.../org/apache/zeppelin/socket/NotebookServer.java | 8 ++++++
.../zeppelin/service/NotebookServiceTest.java | 22 +++++++++++++++
zeppelin-web/README.md | 2 +-
.../org/apache/zeppelin/notebook/NoteManager.java | 32 ++++++++++++++++++++--
.../exception/NotePathAlreadyExistsException.java | 29 ++++++++++++++++++++
.../apache/zeppelin/notebook/NoteManagerTest.java | 32 ++++++++++++++++++++++
7 files changed, 128 insertions(+), 6 deletions(-)
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
index a20036a..dd6bb08 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
@@ -48,6 +48,7 @@ import org.apache.zeppelin.notebook.NoteManager;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.AuthorizationService;
+import org.apache.zeppelin.notebook.exception.NotePathAlreadyExistsException;
import org.apache.zeppelin.notebook.repo.NotebookRepoWithVersionControl;
import org.apache.zeppelin.notebook.scheduler.SchedulerService;
import org.apache.zeppelin.common.Message;
@@ -256,8 +257,12 @@ public class NotebookService {
newNotePath = "/" + newNotePath;
}
}
- notebook.moveNote(noteId, newNotePath, context.getAutheInfo());
- callback.onSuccess(note, context);
+ try {
+ notebook.moveNote(noteId, newNotePath, context.getAutheInfo());
+ callback.onSuccess(note, context);
+ } catch (NotePathAlreadyExistsException e) {
+ callback.onFailure(e, context);
+ }
} else {
callback.onFailure(new NoteNotFoundException(noteId), context);
}
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 cfbfc51..0fd52f7 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
@@ -962,6 +962,14 @@ public class NotebookServer extends WebSocketServlet
broadcastNote(note);
broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles());
}
+
+ @Override
+ public void onFailure(Exception ex, ServiceContext context) throws IOException {
+ super.onFailure(ex, context);
+
+ // If there was a failure, then resend the latest notebook information to update stale UI
+ broadcastNote(getNotebook().getNote(noteId));
+ }
});
}
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
index 4c2156a..7168bbd 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -58,6 +59,7 @@ import org.apache.zeppelin.notebook.NoteInfo;
import org.apache.zeppelin.notebook.NoteManager;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.notebook.exception.NotePathAlreadyExistsException;
import org.apache.zeppelin.notebook.repo.InMemoryNotebookRepo;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.notebook.repo.VFSNotebookRepo;
@@ -332,6 +334,26 @@ public class NotebookServiceTest {
}
@Test
+ public void testRenameNoteRejectsDuplicate() throws IOException {
+ Note note1 = notebookService.createNote("/folder/note1", "test", true, context, callback);
+ assertEquals("note1", note1.getName());
+ verify(callback).onSuccess(note1, context);
+
+ reset(callback);
+ Note note2 = notebookService.createNote("/folder/note2", "test", true, context, callback);
+ assertEquals("note2", note2.getName());
+ verify(callback).onSuccess(note2, context);
+
+ reset(callback);
+ ArgumentCaptor<NotePathAlreadyExistsException> exception = ArgumentCaptor.forClass(NotePathAlreadyExistsException.class);
+ notebookService.renameNote(note1.getId(), "/folder/note2", false, context, callback);
+ verify(callback).onFailure(exception.capture(), any(ServiceContext.class));
+ assertEquals("Note '/folder/note2' existed", exception.getValue().getMessage());
+ verify(callback, never()).onSuccess(any(), any());
+ }
+
+
+ @Test
public void testParagraphOperations() throws IOException {
// create note
Note note1 = notebookService.createNote("note1", "python", false, context, callback);
diff --git a/zeppelin-web/README.md b/zeppelin-web/README.md
index 0194bd7..a269c09 100644
--- a/zeppelin-web/README.md
+++ b/zeppelin-web/README.md
@@ -39,7 +39,7 @@ $ WEB_PORT=YOUR_WEB_DEV_PORT yarn run dev
```sh
# running unit tests
-$ yarn run test
+$ yarn run karma-test
# running e2e tests: make sure that zeppelin instance started (localhost:8080)
$ yarn run e2e
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java
index 779d173..484ee09 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java
@@ -20,6 +20,7 @@ package org.apache.zeppelin.notebook;
import org.apache.commons.lang3.StringUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.notebook.exception.NotePathAlreadyExistsException;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.slf4j.Logger;
@@ -118,6 +119,11 @@ public class NoteManager {
private void addOrUpdateNoteNode(Note note, boolean checkDuplicates) throws IOException {
String notePath = note.getPath();
+
+ if (checkDuplicates && !isNotePathAvailable(notePath)) {
+ throw new NotePathAlreadyExistsException("Note '" + notePath + "' existed");
+ }
+
String[] tokens = notePath.split("/");
Folder curFolder = root;
for (int i = 0; i < tokens.length - 1; ++i) {
@@ -125,9 +131,7 @@ public class NoteManager {
curFolder = curFolder.getOrCreateFolder(tokens[i]);
}
}
- if (checkDuplicates && curFolder.containsNote(tokens[tokens.length - 1])) {
- throw new IOException("Note '" + note.getPath() + "' existed");
- }
+
curFolder.addNote(tokens[tokens.length -1], note);
this.notesInfo.put(note.getId(), note.getPath());
}
@@ -225,6 +229,10 @@ public class NoteManager {
throw new IOException("No metadata found for this note: " + noteId);
}
+ if (!isNotePathAvailable(newNotePath)) {
+ throw new NotePathAlreadyExistsException("Note '" + newNotePath + "' existed");
+ }
+
// move the old NoteNode from notePath to newNotePath
NoteNode noteNode = getNoteNode(notePath);
noteNode.getParent().removeNote(getNoteName(notePath));
@@ -378,6 +386,24 @@ public class NoteManager {
return notePath.substring(pos + 1);
}
+ private boolean isNotePathAvailable(String notePath) {
+ String[] tokens = notePath.split("/");
+ Folder curFolder = root;
+ for (int i = 0; i < tokens.length - 1; ++i) {
+ if (!StringUtils.isBlank(tokens[i])) {
+ curFolder = curFolder.getFolder(tokens[i]);
+ if (curFolder == null) {
+ return true;
+ }
+ }
+ }
+ if (curFolder.containsNote(tokens[tokens.length - 1])) {
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Represent one folder that could contains sub folders and note files.
*/
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/exception/NotePathAlreadyExistsException.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/exception/NotePathAlreadyExistsException.java
new file mode 100644
index 0000000..ee89758
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/exception/NotePathAlreadyExistsException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.notebook.exception;
+
+import java.io.IOException;
+
+public class NotePathAlreadyExistsException extends IOException {
+ private static final long serialVersionUID = -9004313429043423507L;
+
+ public NotePathAlreadyExistsException(final String message) {
+ super(message);
+ }
+
+}
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteManagerTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteManagerTest.java
index 12e1e11..47a7a6b 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteManagerTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NoteManagerTest.java
@@ -1,9 +1,12 @@
package org.apache.zeppelin.notebook;
+import org.apache.zeppelin.notebook.exception.NotePathAlreadyExistsException;
import org.apache.zeppelin.notebook.repo.InMemoryNotebookRepo;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.util.Map;
@@ -13,6 +16,9 @@ import static org.junit.Assert.assertEquals;
public class NoteManagerTest {
private NoteManager noteManager;
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Before
public void setUp() throws IOException {
this.noteManager = new NoteManager(new InMemoryNotebookRepo());
@@ -60,6 +66,32 @@ public class NoteManagerTest {
assertEquals(0, notesInfo.size());
}
+ @Test
+ public void testAddNoteRejectsDuplicatePath() throws IOException {
+ thrown.expect(NotePathAlreadyExistsException.class);
+ thrown.expectMessage("Note '/prod/note' existed");
+
+ Note note1 = createNote("/prod/note");
+ Note note2 = createNote("/prod/note");
+
+ noteManager.addNote(note1, AuthenticationInfo.ANONYMOUS);
+ noteManager.addNote(note2, AuthenticationInfo.ANONYMOUS);
+ }
+
+ @Test
+ public void testMoveNoteRejectsDuplicatePath() throws IOException {
+ thrown.expect(NotePathAlreadyExistsException.class);
+ thrown.expectMessage("Note '/prod/note-1' existed");
+
+ Note note1 = createNote("/prod/note-1");
+ Note note2 = createNote("/prod/note-2");
+
+ noteManager.addNote(note1, AuthenticationInfo.ANONYMOUS);
+ noteManager.addNote(note2, AuthenticationInfo.ANONYMOUS);
+
+ noteManager.moveNote(note2.getId(), "/prod/note-1", AuthenticationInfo.ANONYMOUS);
+ }
+
private Note createNote(String notePath) {
return new Note(notePath, "test", null, null, null, null, null);
}