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 2022/03/14 08:11:14 UTC

[zeppelin] branch master updated: [ZEPPELIN-5661] Support clone a specific revision of a note (#4299)

This is an automated email from the ASF dual-hosted git repository.

zjffdu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new c26ed1a  [ZEPPELIN-5661] Support clone a specific revision of a note (#4299)
c26ed1a is described below

commit c26ed1a0bc0221141d8d216c54507a72a6580e2a
Author: nihua <gu...@foxmail.com>
AuthorDate: Mon Mar 14 16:11:09 2022 +0800

    [ZEPPELIN-5661] Support clone a specific revision of a note (#4299)
---
 docs/usage/rest_api/notebook.md                    |  8 ++-
 .../org/apache/zeppelin/rest/NotebookRestApi.java  |  4 +-
 .../zeppelin/rest/message/NewNoteRequest.java      |  5 ++
 .../apache/zeppelin/service/NotebookService.java   | 23 +++++--
 .../apache/zeppelin/rest/NotebookRestApiTest.java  | 79 +++++++++++++++-------
 .../org/apache/zeppelin/notebook/Notebook.java     | 32 ++++++---
 6 files changed, 113 insertions(+), 38 deletions(-)

diff --git a/docs/usage/rest_api/notebook.md b/docs/usage/rest_api/notebook.md
index c49d805..b669213 100644
--- a/docs/usage/rest_api/notebook.md
+++ b/docs/usage/rest_api/notebook.md
@@ -352,7 +352,8 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete,
     <tr>
       <td>Description</td>
       <td>This ```POST``` method clones a note by the given id and create a new note using the given name
-          or default name if none given.
+          or default name if none given. If what you want to copy is a certain version of note, you need 
+          to specify the revisionId.
           The body field of the returned JSON contains the new note id.
       </td>
     </tr>
@@ -373,7 +374,10 @@ Notebooks REST API supports the following operations: List, Create, Get, Delete,
       <td>
 
 ```json
-{"name": "name of new note"}
+{
+  "name": "name of new note",
+  "revisionId": "revisionId of note to be copied (optional)"
+}
 ```
 </td>
     </tr>
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index 40ad46a..88004f4 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -555,11 +555,13 @@ public class NotebookRestApi extends AbstractRestApi {
     checkIfUserCanWrite(noteId, "Insufficient privileges you cannot clone this note");
     NewNoteRequest request = NewNoteRequest.fromJson(message);
     String newNoteName = null;
+    String revisionId = null;
     if (request != null) {
       newNoteName = request.getName();
+      revisionId = request.getRevisionId();
     }
     AuthenticationInfo subject = new AuthenticationInfo(authenticationService.getPrincipal());
-    String newNoteId = notebookService.cloneNote(noteId, newNoteName, getServiceContext(),
+    String newNoteId = notebookService.cloneNote(noteId, revisionId, newNoteName, getServiceContext(),
             new RestServiceCallback<Note>() {
               @Override
               public void onSuccess(Note newNote, ServiceContext context) throws IOException {
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewNoteRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewNoteRequest.java
index 85744dc..1ef9da3 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewNoteRequest.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NewNoteRequest.java
@@ -33,6 +33,7 @@ public class NewNoteRequest implements JsonSerializable {
   private String defaultInterpreterGroup;
   private boolean addingEmptyParagraph = false;
   private List<NewParagraphRequest> paragraphs;
+  private String revisionId;
 
   public NewNoteRequest (){
   }
@@ -53,6 +54,10 @@ public class NewNoteRequest implements JsonSerializable {
     return paragraphs;
   }
 
+  public String getRevisionId() {
+    return revisionId;
+  }
+
   public String toJson() {
     return GSON.toJson(this);
   }
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 02a4bbd..dfc75eb 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
@@ -317,15 +317,30 @@ public class NotebookService {
   }
 
   public String cloneNote(String noteId,
-                        String newNotePath,
-                        ServiceContext context,
-                        ServiceCallback<Note> callback) throws IOException {
+                          String newNotePath,
+                          ServiceContext context,
+                          ServiceCallback<Note> callback) throws IOException {
+    return cloneNote(noteId, "", newNotePath, context, callback);
+  }
+
+
+  public String cloneNote(String noteId,
+                          String revisionId,
+                          String newNotePath,
+                          ServiceContext context,
+                          ServiceCallback<Note> callback) throws IOException {
     //TODO(zjffdu) move these to Notebook
     if (StringUtils.isBlank(newNotePath)) {
       newNotePath = "/Cloned Note_" + noteId;
+      if(StringUtils.isNotEmpty(revisionId)) {
+        // If cloning a revision of the note,
+        // append the short commit id of revision to newNoteName
+        // to distinguish which commit to be copied.
+        newNotePath += "_" + revisionId.substring(0, 7);
+      }
     }
     try {
-      String newNoteId = notebook.cloneNote(noteId, normalizeNotePath(newNotePath),
+      String newNoteId = notebook.cloneNote(noteId, revisionId, normalizeNotePath(newNotePath),
           context.getAutheInfo());
       return notebook.processNote(newNoteId,
         newNote -> {
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
index 62398c0..e186ae9 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java
@@ -43,10 +43,7 @@ import org.junit.runners.MethodSorters;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.util.EntityUtils;
@@ -895,33 +892,69 @@ public class NotebookRestApiTest extends AbstractTestRestApi {
   public void testCloneNote() throws IOException {
     LOG.info("Running testCloneNote");
     String note1Id = null;
-    String clonedNoteId = null;
+    List<String> clonedNoteIds = new ArrayList<>();
+    String text1 = "%text clone note";
+    String text2 = "%text clone revision of note";
     try {
-      note1Id = TestUtils.getInstance(Notebook.class).createNote("note1", anonymous);
-      CloseableHttpResponse post = httpPost("/notebook/" + note1Id, "");
-      String postResponse = EntityUtils.toString(post.getEntity(), StandardCharsets.UTF_8);
-      LOG.info("testCloneNote response\n" + postResponse);
-      assertThat(post, isAllowed());
-      Map<String, Object> resp = gson.fromJson(postResponse,
-              new TypeToken<Map<String, Object>>() {}.getType());
-      clonedNoteId = (String) resp.get("body");
-      post.close();
+      Notebook notebook = TestUtils.getInstance(Notebook.class);
+      note1Id = notebook.createNote("note1", anonymous);
 
-      CloseableHttpResponse get = httpGet("/notebook/" + clonedNoteId);
-      assertThat(get, isAllowed());
-      Map<String, Object> resp2 = gson.fromJson(EntityUtils.toString(get.getEntity(), StandardCharsets.UTF_8),
-              new TypeToken<Map<String, Object>>() {}.getType());
-      Map<String, Object> resp2Body = (Map<String, Object>) resp2.get("body");
+      // add text and commit note
+      NotebookRepoWithVersionControl.Revision first_commit =
+              notebook.processNote(note1Id, note -> {
+                Paragraph p1 = note.addNewParagraph(anonymous);
+                p1.setText(text2);
+                notebook.saveNote(note, AuthenticationInfo.ANONYMOUS);
+                return notebook.checkpointNote(note.getId(), note.getPath(), "first commit", anonymous);
+              });
 
-      //    assertEquals(resp2Body.get("name"), "Note " + clonedNoteId);
-      get.close();
+      // change the text of note
+      notebook.processNote(note1Id, note -> {
+        note.getParagraph(0).setText(text1);
+        return null;
+      });
+
+      // Clone a note
+      CloseableHttpResponse post1 = httpPost("/notebook/" + note1Id, "");
+      // Clone a revision of note
+      CloseableHttpResponse post2 =
+              httpPost("/notebook/" + note1Id, "{ revisionId: " + first_commit.id + "}");
+
+      // Verify the responses
+      for (int i = 0; i < 2; i++) {
+        CloseableHttpResponse post = Arrays.asList(post1, post2).get(i);
+        String text = Arrays.asList(text1, text2).get(i);
+
+        String postResponse = EntityUtils.toString(post.getEntity(), StandardCharsets.UTF_8);
+        LOG.info("testCloneNote response: {}", postResponse);
+        assertThat(post, isAllowed());
+        Map<String, Object> resp = gson.fromJson(postResponse,
+                new TypeToken<Map<String, Object>>() {
+                }.getType());
+        clonedNoteIds.add((String) resp.get("body"));
+        post.close();
+
+        CloseableHttpResponse get = httpGet("/notebook/" + clonedNoteIds.get(clonedNoteIds.size() - 1));
+        assertThat(get, isAllowed());
+        Map<String, Object> resp2 = gson.fromJson(EntityUtils.toString(get.getEntity(), StandardCharsets.UTF_8),
+                new TypeToken<Map<String, Object>>() {
+                }.getType());
+        Map<String, Object> resp2Body = (Map<String, Object>) resp2.get("body");
+        List<Map<String, String>> paragraphs = (List<Map<String, String>>) resp2Body.get("paragraphs");
+        // Verify that the original and copied text are consistent
+        assertEquals(text, paragraphs.get(0).get("text"));
+        //    assertEquals(resp2Body.get("name"), "Note " + clonedNoteId);
+        get.close();
+      }
     } finally {
       // cleanup
       if (null != note1Id) {
         TestUtils.getInstance(Notebook.class).removeNote(note1Id, anonymous);
       }
-      if (null != clonedNoteId) {
-        TestUtils.getInstance(Notebook.class).removeNote(clonedNoteId, anonymous);
+      if (null != clonedNoteIds) {
+        for (String clonedNoteId : clonedNoteIds) {
+          TestUtils.getInstance(Notebook.class).removeNote(clonedNoteId, anonymous);
+        }
       }
     }
   }
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
index b985b1e..ad285cc 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
@@ -32,6 +32,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import javax.inject.Inject;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObject;
@@ -310,25 +311,40 @@ public class Notebook {
    * @throws IOException
    */
   public String cloneNote(String sourceNoteId, String newNotePath, AuthenticationInfo subject)
+          throws IOException {
+    return cloneNote(sourceNoteId, "", newNotePath, subject);
+  }
+
+
+  public String cloneNote(String sourceNoteId, String revisionId, String newNotePath, AuthenticationInfo subject)
       throws IOException {
     return processNote(sourceNoteId,
       sourceNote -> {
         if (sourceNote == null) {
           throw new IOException("Source note: " + sourceNoteId + " not found");
         }
+        Note note;
+        if(StringUtils.isNotEmpty(revisionId)) {
+            note = getNoteByRevision(sourceNote.getId(), sourceNote.getPath(), revisionId, subject);
+        } else {
+          note = sourceNote;
+        }
+        if (note == null) {
+          throw new IOException("Source note: " + sourceNoteId + " revisionId " + revisionId + " not found");
+        }
         String newNoteId = createNote(newNotePath, subject, false);
         processNote(newNoteId,
           newNote -> {
-            List<Paragraph> paragraphs = sourceNote.getParagraphs();
+            List<Paragraph> paragraphs = note.getParagraphs();
             for (Paragraph p : paragraphs) {
               newNote.addCloneParagraph(p, subject);
             }
 
-            newNote.setConfig(new HashMap<>(sourceNote.getConfig()));
-            newNote.setInfo(new HashMap<>(sourceNote.getInfo()));
-            newNote.setDefaultInterpreterGroup(sourceNote.getDefaultInterpreterGroup());
-            newNote.setNoteForms(new HashMap<>(sourceNote.getNoteForms()));
-            newNote.setNoteParams(new HashMap<>(sourceNote.getNoteParams()));
+            newNote.setConfig(new HashMap<>(note.getConfig()));
+            newNote.setInfo(new HashMap<>(note.getInfo()));
+            newNote.setDefaultInterpreterGroup(note.getDefaultInterpreterGroup());
+            newNote.setNoteForms(new HashMap<>(note.getNoteForms()));
+            newNote.setNoteParams(new HashMap<>(note.getNoteParams()));
             newNote.setRunning(false);
 
             saveNote(newNote, subject);
@@ -528,11 +544,11 @@ public class Notebook {
     }
   }
 
-  public Note getNoteByRevision(String noteId, String noteName,
+  public Note getNoteByRevision(String noteId, String notePath,
                                 String revisionId, AuthenticationInfo subject)
       throws IOException {
     if (((NotebookRepoSync) notebookRepo).isRevisionSupportedInDefaultRepo()) {
-      return ((NotebookRepoWithVersionControl) notebookRepo).get(noteId, noteName,
+      return ((NotebookRepoWithVersionControl) notebookRepo).get(noteId, notePath,
           revisionId, subject);
     } else {
       return null;