ZEPPELIN-501 Notebook search ### What is this PR for? This PR has for goal to allow the user to search through the code in all the paragraphs *and notebook names* in all the notebooks
It add a simple 'search bar' to the nav-bar of Zeppelin WebApp, and an in-memory fulltext search index of paragraphs to the backend. The search is pretty basic now, fine-tuning it for better search over all types of source code will be a subject of further work. ### What type of PR is it? Feature ### Todos * [x] - Fix typos :dancer: b853aa6 * [x] - fix js issue in js console felizbear 29da337 * [x] - update index on paragraph CRUD: - [x] Read (initial work) - [x] Create\Delete 825b266 - [x] Update e915a69 - [x] Delete paragraph * [x] - add license to zeppelin-distribution/src/bin_license/LICENSE (backend for lucene, not sure about angular-resource as it is part of the AngularJS codebase, but will add just in case) c00b516 * [x] - add missing Apache headers ded9c3b felizbear 29da337 * [x] - fix CI (~~was failing RAT on zengine Too many files with unapproved license: 2~~, now flacky integration test AKA [ZEPPELIN-510](https://issues.apache.org/jira/browse/ZEPPELIN-510)) * [x] - index notebook names e80c3e5, felizbear 29da337 * [x] - make NotebookRepoSync.sync() package private again 5a18bc8 * [x] - NPE on persisting changes of existing notebook * [x] - reduce log verbosity ### Is there a relevant Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-501 ### How should this be tested? Outline the steps to test the PR here. ### Screenshots (if appropriate)   ### Questions: * Does the licenses files need update? Yes, added to license files * Is there breaking changes for older versions? No * Does this needs documentation? Yes This work is a collaboration with felizbear who contributed major parts of the frontend changes. Author: Alexander Bezzubov <[email protected]> Author: felizbear <[email protected]> Author: Alexander <[email protected]> Closes #534 from bzz/notebook-search and squashes the following commits: c7f1d35 [Alexander Bezzubov] ZEPPELIN-501: refactoring, extract SearchService interface + Lucen-based impl e3af25c [Alexander Bezzubov] ZEPPELIN-501: muting more search logs info->debug c77d53b [Alexander Bezzubov] ZEPPELIN-501: fix + test for bug in index key structure 78f69a3 [Alexander Bezzubov] ZEPPELIN-501: update index on paragraph delete 73a28bb [Alexander Bezzubov] ZEPPELIN-501: fix NPE on Note creation by NotebookRepo, \wo DI constructor cf44b1f [Alexander Bezzubov] ZEPPELIN-501: fix NPE on double-delete d2b44ac [Alexander Bezzubov] ZEPPELIN-501: stop printing whole note in logs f30e6c8 [Alexander Bezzubov] Merge branch 'master' into notebook-search da69c07 [Alexander Bezzubov] ZEPPELIN-501: validate notes before update index 5f47890 [Alexander Bezzubov] ZEPPELIN-501: fix bug (merge artefact) that failed CI befor e915a69 [Alexander Bezzubov] ZEPPELIN-501: update notebooks on save + tests 3f20904 [Alexander Bezzubov] ZEPPELIN-501: refactoring, compiler warning in Paragraph.java 40cf9e8 [Alexander Bezzubov] ZEPPELIN-501: refactoring, cleaning compiler warnings in Notebook.java 6a3906f [Alexander Bezzubov] Merge branch 'master' into notebook-search 2b2f8dc [Alexander Bezzubov] Merge branch 'master' into notebook-search 44235eb [Alexander Bezzubov] ZEPPELIN-501: refactoring, cleaning compiler warnings in Note.java eb7878a [Alexander Bezzubov] Merge branch 'master' into notebook-search 0ce8a92 [Alexander Bezzubov] ZEPPELIN-501: add missing logger 6da1dc9 [Alexander Bezzubov] Merge branch 'master' into notebook-search 8c0f29a [Alexander Bezzubov] ZEPPELIN-501: fixing NPE in tests \w mocks 825b266 [Alexander Bezzubov] ZEPPELIN-501: refactoring + handling index Create\Delete b13d5fb [Alexander Bezzubov] Merge branch 'master' into notebook-search 009b290 [Alexander Bezzubov] ZEPPELIN-501: muting logs back 0efc00e [Alexander Bezzubov] Merge branch 'master' into notebook-search b0b2c54 [Alexander Bezzubov] ZEPPELIN-501: refactoring, renames + verbose logging ON 36b2467 [Alexander Bezzubov] ZEPPELIN-501: add default val for Note.name c7ae983 [Alexander Bezzubov] Merge branch 'master' into notebook-search ded9c3b [Alexander Bezzubov] ZEPPELIN-501: adding missing license headers cd2173e [Alexander Bezzubov] ZEPPELIN-501: tixing fypo in docs, again :see_no_evil: 1952847 [Alexander Bezzubov] ZEPPELIN-501: minor test update 6180c86 [Alexander Bezzubov] ZEPPELIN-501: mute logs, invalid notes do not fail all indexing 29da337 [Alexander] Merge pull request #7 from felizbear/search-fix c00b516 [Alexander Bezzubov] ZEPPELIN-501: update LICENCE file with new deps 5a18bc8 [Alexander Bezzubov] ZEPPELIN-501: restore NotebookRepoSync.sync() visibility + compiler warning cleanup fcbff3d [felizbear] add missing apache license info to a source file 00f0315 [felizbear] handle notebooks is search results 7d06686 [felizbear] fix search-related bug in notebook controller e80c3e5 [Alexander Bezzubov] ZEPPELIN-501: Indexing notebook names b853aa6 [Alexander Bezzubov] ZEPPELIN-501: fixing typos in docs 82c7dd7 [Alexander Bezzubov] ZEPPELIN-501: reverting accidental changes 71ec51f [felizbear] clear search field on navigation to home view (/) 1e1357c [felizbear] redirect to notebook and scroll to paragraph from search view 09d44d2 [Alexander Bezzubov] Search: re-index note on every change b2b93c4 [Alexander Bezzubov] Search: refatoring, move SearchService from NotebookRepoSync -> Notebook 08fe806 [Alexander Bezzubov] Search: adding tests 63a4e05 [Alexander Bezzubov] Search: adding search API docs 98f4e59 [Alexander Bezzubov] Search: refactoring, rename fragment -> snippet 5c1e3e4 [Alexander Bezzubov] Refatoring - removing old annotations e363ed4 [Alexander Bezzubov] Search: make jshint happy 7aad5cf [Alexander Bezzubov] Search: make checkstyle happy 6ccd6f1 [Alexander Bezzubov] Search: nuke compiler warnings in NotebookRestApi aa5ddb3 [felizbear] center search results on screen 227c6b4 [felizbear] update search results view: add panels for results 865925c [felizbear] highlight syntax and search terms in search results 9ca8628 [Alexander Bezzubov] Search: merge first 3 fragments + full paragraph in search result 11127f0 [felizbear] style search widget for notebook search db246fa [Alexander Bezzubov] Search: highlighting added using fragments c2c2a52 [Alexander Bezzubov] Search: backend indexing using Lucene added to zengine 163a465 [Alexander Bezzubov] Search: add search/result-list and switch to bc2458a [Alexander Bezzubov] Get rid of compiler warnings 3900b60 [Alexander Bezzubov] Search: disabling UI on disconnect 7880237 [Alexander Bezzubov] Search: backend REST API scetch /notebooks/search?q=... added c5928f9 [Alexander Bezzubov] Search: form added to navbar in frontend Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/82de508d Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/82de508d Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/82de508d Branch: refs/heads/master Commit: 82de508d727761a9d95ce506a23e39b372f82a93 Parents: d439688 Author: Alexander Bezzubov <[email protected]> Authored: Wed Dec 23 17:48:03 2015 +0900 Committer: Alexander Bezzubov <[email protected]> Committed: Wed Dec 23 19:27:41 2015 +0900 ---------------------------------------------------------------------- docs/rest-api/rest-notebook.md | 29 ++ zeppelin-distribution/src/bin_license/LICENSE | 3 +- .../java/org/apache/zeppelin/scheduler/Job.java | 1 - zeppelin-server/pom.xml | 34 +- .../apache/zeppelin/rest/NotebookRestApi.java | 116 +++--- .../apache/zeppelin/rest/ZeppelinRestApi.java | 1 - .../apache/zeppelin/server/ZeppelinServer.java | 11 +- .../apache/zeppelin/socket/NotebookServer.java | 8 +- .../zeppelin/rest/ZeppelinRestApiTest.java | 15 +- zeppelin-web/bower.json | 1 + zeppelin-web/src/app/app.js | 11 +- .../src/app/notebook/notebook.controller.js | 27 +- .../src/app/search/result-list.controller.js | 119 ++++++ zeppelin-web/src/app/search/result-list.html | 42 ++ zeppelin-web/src/app/search/search.css | 37 ++ .../src/components/navbar/navbar.controller.js | 16 +- zeppelin-web/src/components/navbar/navbar.html | 27 ++ .../components/searchService/search.service.js | 29 ++ zeppelin-web/src/index.html | 4 + zeppelin-web/test/karma.conf.js | 1 + zeppelin-zengine/pom.xml | 30 ++ .../java/org/apache/zeppelin/notebook/Note.java | 59 +-- .../org/apache/zeppelin/notebook/Notebook.java | 86 ++-- .../org/apache/zeppelin/notebook/Paragraph.java | 5 +- .../notebook/repo/NotebookRepoSync.java | 24 +- .../apache/zeppelin/search/LuceneSearch.java | 391 +++++++++++++++++++ .../apache/zeppelin/search/SearchService.java | 87 +++++ .../apache/zeppelin/notebook/NotebookTest.java | 9 +- .../notebook/repo/NotebookRepoSyncTest.java | 6 +- .../notebook/repo/VFSNotebookRepoTest.java | 23 +- .../zeppelin/search/LuceneSearchTest.java | 259 ++++++++++++ 31 files changed, 1336 insertions(+), 175 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/docs/rest-api/rest-notebook.md ---------------------------------------------------------------------- diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 7393c5a..fade802 100644 --- a/docs/rest-api/rest-notebook.md +++ b/docs/rest-api/rest-notebook.md @@ -556,4 +556,33 @@ limitations under the License. <td><pre>{"status":"OK","body":"* * * * * ?"}</pre></td> </tr> </table> + + <table class="table-configuration"> + <col width="200"> + <tr> + <th>Full-text search through the paragraphs in all notebooks</th> + <th></th> + </tr> + <tr> + <td>Description</td> + <td>```GET``` request will return list of matching paragraphs + </td> + </tr> + <tr> + <td>URL</td> + <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/search?q=[query]```</td> + </tr> + <tr> + <td>Success code</td> + <td>200</td> + </tr> + <tr> + <td>Fail code</td> + <td> 500 </td> + </tr> + <tr> + <td>Sample JSON response </td> + <td><pre>{"status":"OK", body: [{"id":"<noteId>/paragraph/<paragraphId>", "name":"Notebook Name", "snippet":"", "text":""}]}</pre></td> + </tr> + </table> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-distribution/src/bin_license/LICENSE ---------------------------------------------------------------------- diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE index 1cce5dc..52ce3eb 100644 --- a/zeppelin-distribution/src/bin_license/LICENSE +++ b/zeppelin-distribution/src/bin_license/LICENSE @@ -44,6 +44,7 @@ The following components are provided under Apache License. (Apache 2.0) Apache Tajo (http://tajo.apache.org/) (Apache 2.0) Apache Flink (http://flink.apache.org/) (Apache 2.0) Apache Thrift (http://thrift.apache.org/) + (Apache 2.0) Apache Lucene (https://lucene.apache.org/) (Apache 2.0) Apache Zookeeper (org.apache.zookeeper:zookeeper:jar:3.4.5 - http://zookeeper.apache.org/) (Apache 2.0) Chill (com.twitter:chill-java:jar:0.5.0 - https://github.com/twitter/chill/) (Apache 2.0) Codehaus Plexus (org.codehaus.plexus:plexus:jar:1.5.6 - https://codehaus-plexus.github.io/) @@ -129,10 +130,10 @@ The following components are provided under the MIT License. (The MIT License) Objenesis (org.objenesis:objenesis:2.1 - https://github.com/easymock/objenesis) - Copyright (c) 2006-2015 the original author and authors (The MIT License) JCL 1.1.1 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.5 - http://www.slf4j.org) (The MIT License) JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.5 - http://www.slf4j.org) + (The MIT License) angular-resource (angular-resource - https://github.com/angular/angular.js/tree/master/src/ngResource) (The MIT License) minimal-json (com.eclipsesource.minimal-json:minimal-json:0.9.4 - https://github.com/ralfstx/minimal-json) - ======================================================================== BSD-style licenses ======================================================================== http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java ---------------------------------------------------------------------- diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java index 4c8c70a..c803d78 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/Job.java @@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory; * and saving/loading jobs from disk. * Changing/adding/deleting non transitive field name need consideration of that. * - * @author Leemoonsoo */ public abstract class Job { /** http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-server/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 0563374..e77ee6c 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -197,6 +197,22 @@ <version>2.2.1</version> </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-servlet</artifactId> + <version>1.13</version> + </dependency> + + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>2.0-m10</version> + </dependency> + + <dependency> + <groupId>org.scala-lang</groupId> + <artifactId>scala-library</artifactId> + </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> @@ -241,23 +257,6 @@ </dependency> <dependency> - <groupId>com.sun.jersey</groupId> - <artifactId>jersey-servlet</artifactId> - <version>1.13</version> - </dependency> - - <dependency> - <groupId>javax.ws.rs</groupId> - <artifactId>javax.ws.rs-api</artifactId> - <version>2.0-m10</version> - </dependency> - - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - </dependency> - - <dependency> <groupId>org.scalatest</groupId> <artifactId>scalatest_2.10</artifactId> <version>2.1.1</version> @@ -268,6 +267,7 @@ <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.0</version> + <scope>test</scope> </dependency> </dependencies> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java ---------------------------------------------------------------------- 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 d9e7cf7..fb4e994 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 @@ -22,19 +22,29 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.ws.rs.*; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.lang3.StringUtils; -import org.apache.zeppelin.display.Input; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.Paragraph; -import org.apache.zeppelin.rest.message.*; +import org.apache.zeppelin.rest.message.CronRequest; +import org.apache.zeppelin.rest.message.InterpreterSettingListForNoteBind; +import org.apache.zeppelin.rest.message.NewNotebookRequest; +import org.apache.zeppelin.rest.message.NewParagraphRequest; +import org.apache.zeppelin.rest.message.RunParagraphWithParametersRequest; +import org.apache.zeppelin.search.SearchService; import org.apache.zeppelin.server.JsonResponse; -import org.apache.zeppelin.server.ZeppelinServer; import org.apache.zeppelin.socket.NotebookServer; import org.quartz.CronExpression; import org.slf4j.Logger; @@ -49,17 +59,18 @@ import com.google.gson.reflect.TypeToken; @Path("/notebook") @Produces("application/json") public class NotebookRestApi { - Logger logger = LoggerFactory.getLogger(NotebookRestApi.class); + private static final Logger LOG = LoggerFactory.getLogger(NotebookRestApi.class); Gson gson = new Gson(); private Notebook notebook; private NotebookServer notebookServer; + private SearchService notebookIndex; public NotebookRestApi() {} - public NotebookRestApi(Notebook notebook, NotebookServer notebookServer) { - + public NotebookRestApi(Notebook notebook, NotebookServer notebookServer, SearchService search) { this.notebook = notebook; this.notebookServer = notebookServer; + this.notebookIndex = search; } /** @@ -71,7 +82,7 @@ public class NotebookRestApi { public Response bind(@PathParam("noteId") String noteId, String req) throws IOException { List<String> settingIdList = gson.fromJson(req, new TypeToken<List<String>>(){}.getType()); notebook.bindInterpretersToNote(noteId, settingIdList); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -114,14 +125,14 @@ public class NotebookRestApi { ); } } - return new JsonResponse(Status.OK, "", settingList).build(); + return new JsonResponse<>(Status.OK, "", settingList).build(); } @GET @Path("/") public Response getNotebookList() throws IOException { List<Map<String, String>> notesInfo = notebookServer.generateNotebooksInfo(); - return new JsonResponse(Status.OK, "", notesInfo ).build(); + return new JsonResponse<>(Status.OK, "", notesInfo ).build(); } @GET @@ -129,10 +140,10 @@ public class NotebookRestApi { public Response getNotebook(@PathParam("notebookId") String notebookId) throws IOException { Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } - return new JsonResponse(Status.OK, "", note).build(); + return new JsonResponse<>(Status.OK, "", note).build(); } /** @@ -144,7 +155,7 @@ public class NotebookRestApi { @POST @Path("/") public Response createNote(String message) throws IOException { - logger.info("Create new notebook by JSON {}" , message); + LOG.info("Create new notebook by JSON {}" , message); NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); Note note = notebook.createNote(); @@ -165,7 +176,7 @@ public class NotebookRestApi { note.persist(); notebookServer.broadcastNote(note); notebookServer.broadcastNoteList(); - return new JsonResponse(Status.CREATED, "", note.getId() ).build(); + return new JsonResponse<>(Status.CREATED, "", note.getId() ).build(); } /** @@ -177,7 +188,7 @@ public class NotebookRestApi { @DELETE @Path("{notebookId}") public Response deleteNote(@PathParam("notebookId") String notebookId) throws IOException { - logger.info("Delete notebook {} ", notebookId); + LOG.info("Delete notebook {} ", notebookId); if (!(notebookId.isEmpty())) { Note note = notebook.getNote(notebookId); if (note != null) { @@ -185,7 +196,7 @@ public class NotebookRestApi { } } notebookServer.broadcastNoteList(); - return new JsonResponse(Status.OK, "").build(); + return new JsonResponse<>(Status.OK, "").build(); } /** @@ -198,14 +209,14 @@ public class NotebookRestApi { @Path("{notebookId}") public Response cloneNote(@PathParam("notebookId") String notebookId, String message) throws IOException, CloneNotSupportedException, IllegalArgumentException { - logger.info("clone notebook by JSON {}" , message); + LOG.info("clone notebook by JSON {}" , message); NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); String newNoteName = request.getName(); Note newNote = notebook.cloneNote(notebookId, newNoteName); notebookServer.broadcastNote(newNote); notebookServer.broadcastNoteList(); - return new JsonResponse(Status.CREATED, "", newNote.getId()).build(); + return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build(); } /** @@ -218,14 +229,14 @@ public class NotebookRestApi { @Path("job/{notebookId}") public Response runNoteJobs(@PathParam("notebookId") String notebookId) throws IOException, IllegalArgumentException { - logger.info("run notebook jobs {} ", notebookId); + LOG.info("run notebook jobs {} ", notebookId); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } note.runAll(); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -238,10 +249,10 @@ public class NotebookRestApi { @Path("job/{notebookId}") public Response stopNoteJobs(@PathParam("notebookId") String notebookId) throws IOException, IllegalArgumentException { - logger.info("stop notebook jobs {} ", notebookId); + LOG.info("stop notebook jobs {} ", notebookId); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } for (Paragraph p : note.getParagraphs()) { @@ -249,7 +260,7 @@ public class NotebookRestApi { p.abort(); } } - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -262,19 +273,21 @@ public class NotebookRestApi { @Path("job/{notebookId}") public Response getNoteJobStatus(@PathParam("notebookId") String notebookId) throws IOException, IllegalArgumentException { - logger.info("get notebook job status."); + LOG.info("get notebook job status."); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } - return new JsonResponse(Status.OK, null, note.generateParagraphsInfo()).build(); + return new JsonResponse<>(Status.OK, null, note.generateParagraphsInfo()).build(); } /** * Run paragraph job REST API + * * @param message - JSON with params if user wants to update dynamic form's value * null, empty string, empty json if user doesn't want to update + * * @return JSON with status.OK * @throws IOException, IllegalArgumentException */ @@ -284,16 +297,16 @@ public class NotebookRestApi { @PathParam("paragraphId") String paragraphId, String message) throws IOException, IllegalArgumentException { - logger.info("run paragraph job {} {} {}", notebookId, paragraphId, message); + LOG.info("run paragraph job {} {} {}", notebookId, paragraphId, message); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } Paragraph paragraph = note.getParagraph(paragraphId); if (paragraph == null) { - return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); } // handle params if presented @@ -308,7 +321,7 @@ public class NotebookRestApi { } note.run(paragraph.getId()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -322,18 +335,18 @@ public class NotebookRestApi { public Response stopParagraph(@PathParam("notebookId") String notebookId, @PathParam("paragraphId") String paragraphId) throws IOException, IllegalArgumentException { - logger.info("stop paragraph job {} ", notebookId); + LOG.info("stop paragraph job {} ", notebookId); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } Paragraph p = note.getParagraph(paragraphId); if (p == null) { - return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); } p.abort(); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -346,18 +359,18 @@ public class NotebookRestApi { @Path("cron/{notebookId}") public Response registerCronJob(@PathParam("notebookId") String notebookId, String message) throws IOException, IllegalArgumentException { - logger.info("Register cron job note={} request cron msg={}", notebookId, message); + LOG.info("Register cron job note={} request cron msg={}", notebookId, message); CronRequest request = gson.fromJson(message, CronRequest.class); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } if (!CronExpression.isValidExpression(request.getCronString())) { - return new JsonResponse(Status.BAD_REQUEST, "wrong cron expressions.").build(); + return new JsonResponse<>(Status.BAD_REQUEST, "wrong cron expressions.").build(); } Map<String, Object> config = note.getConfig(); @@ -365,7 +378,7 @@ public class NotebookRestApi { note.setConfig(config); notebook.refreshCron(note.id()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -378,11 +391,11 @@ public class NotebookRestApi { @Path("cron/{notebookId}") public Response removeCronJob(@PathParam("notebookId") String notebookId) throws IOException, IllegalArgumentException { - logger.info("Remove cron job note {}", notebookId); + LOG.info("Remove cron job note {}", notebookId); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } Map<String, Object> config = note.getConfig(); @@ -390,7 +403,7 @@ public class NotebookRestApi { note.setConfig(config); notebook.refreshCron(note.id()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -403,13 +416,26 @@ public class NotebookRestApi { @Path("cron/{notebookId}") public Response getCronJob(@PathParam("notebookId") String notebookId) throws IOException, IllegalArgumentException { - logger.info("Get cron job note {}", notebookId); + LOG.info("Get cron job note {}", notebookId); Note note = notebook.getNote(notebookId); if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); + return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); } - return new JsonResponse(Status.OK, note.getConfig().get("cron")).build(); + return new JsonResponse<>(Status.OK, note.getConfig().get("cron")).build(); } + + /** + * Search for a Notes + */ + @GET + @Path("search") + public Response search(@QueryParam("q") String queryTerm) { + LOG.info("Searching notebooks for: {}", queryTerm); + List<Map<String, String>> notebooksFound = notebookIndex.query(queryTerm); + LOG.info("{} notbooks found", notebooksFound.size()); + return new JsonResponse<>(Status.OK, notebooksFound).build(); + } + } http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-server/src/main/java/org/apache/zeppelin/rest/ZeppelinRestApi.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/ZeppelinRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/ZeppelinRestApi.java index 367f923..9a0b883 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/ZeppelinRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/ZeppelinRestApi.java @@ -24,7 +24,6 @@ import javax.ws.rs.core.Response; /** * Zeppelin root rest api endpoint. * - * @author anthonycorbacho * @since 0.3.4 */ @Path("/") http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java index 7286b35..fd115ee 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -38,6 +38,8 @@ import org.apache.zeppelin.rest.InterpreterRestApi; import org.apache.zeppelin.rest.NotebookRestApi; import org.apache.zeppelin.rest.ZeppelinRestApi; import org.apache.zeppelin.scheduler.SchedulerFactory; +import org.apache.zeppelin.search.SearchService; +import org.apache.zeppelin.search.LuceneSearch; import org.apache.zeppelin.socket.NotebookServer; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Handler; @@ -69,17 +71,18 @@ public class ZeppelinServer extends Application { private SchedulerFactory schedulerFactory; private InterpreterFactory replFactory; private NotebookRepo notebookRepo; + private SearchService notebookIndex; public ZeppelinServer() throws Exception { - LOG.info("Constructor starteds"); ZeppelinConfiguration conf = ZeppelinConfiguration.create(); this.schedulerFactory = new SchedulerFactory(); this.replFactory = new InterpreterFactory(conf, notebookWsServer); this.notebookRepo = new NotebookRepoSync(conf); + this.notebookIndex = new LuceneSearch(); - notebook = new Notebook(conf, notebookRepo, schedulerFactory, replFactory, notebookWsServer); - LOG.info("Constructor finished"); + notebook = new Notebook(conf, + notebookRepo, schedulerFactory, replFactory, notebookWsServer, notebookIndex); } public static void main(String[] args) throws InterruptedException { @@ -264,7 +267,7 @@ public class ZeppelinServer extends Application { ZeppelinRestApi root = new ZeppelinRestApi(); singletons.add(root); - NotebookRestApi notebookApi = new NotebookRestApi(notebook, notebookWsServer); + NotebookRestApi notebookApi = new NotebookRestApi(notebook, notebookWsServer, notebookIndex); singletons.add(notebookApi); InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/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 a010e58..554f68c 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 @@ -15,6 +15,7 @@ * limitations under the License. */ package org.apache.zeppelin.socket; + import java.io.IOException; import java.net.URISyntaxException; import java.net.UnknownHostException; @@ -49,14 +50,14 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; import com.google.gson.Gson; + /** * Zeppelin websocket service. * */ public class NotebookServer extends WebSocketServlet implements NotebookSocketListener, JobListenerFactory, AngularObjectRegistryListener { - private static final Logger LOG = LoggerFactory - .getLogger(NotebookServer.class); + private static final Logger LOG = LoggerFactory.getLogger(NotebookServer.class); Gson gson = new Gson(); final Map<String, List<NotebookSocket>> noteSocketMap = new HashMap<>(); final Queue<NotebookSocket> connectedSockets = new ConcurrentLinkedQueue<>(); @@ -64,9 +65,9 @@ public class NotebookServer extends WebSocketServlet implements private Notebook notebook() { return ZeppelinServer.notebook; } + @Override public boolean checkOrigin(HttpServletRequest request, String origin) { - try { return SecurityUtils.isValidOrigin(origin, ZeppelinConfiguration.create()); } catch (UnknownHostException e) { @@ -74,7 +75,6 @@ public class NotebookServer extends WebSocketServlet implements } catch (URISyntaxException e) { e.printStackTrace(); } - return false; } http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java index caac5a0..2bca9c0 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java @@ -17,9 +17,13 @@ package org.apache.zeppelin.rest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,13 +32,10 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.lang3.StringUtils; -import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Paragraph; -import org.apache.zeppelin.rest.message.NewParagraphRequest; import org.apache.zeppelin.scheduler.Job.Status; -import org.apache.zeppelin.server.JsonResponse; import org.apache.zeppelin.server.ZeppelinServer; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -45,13 +46,9 @@ import org.junit.runners.MethodSorters; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import static org.junit.Assert.*; - /** * BASIC Zeppelin rest api tests * - * @author anthonycorbacho - * */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class ZeppelinRestApiTest extends AbstractTestRestApi { http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/bower.json ---------------------------------------------------------------------- diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 4e5c353..ee9ea46 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -11,6 +11,7 @@ "angular-animate": "1.3.8", "angular-touch": "1.3.8", "angular-route": "1.3.8", + "angular-resource": "1.3.8", "angular-bootstrap": "~0.13.0", "angular-websocket": "~1.0.13", "ace-builds": "1.1.9", http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/app/app.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 64d43bb..92e7345 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -32,7 +32,8 @@ angular.module('zeppelinWebApp', [ 'puElasticInput', 'xeditable', 'ngToast', - 'focus-if' + 'focus-if', + 'ngResource' ]) .filter('breakFilter', function() { return function (text) { @@ -50,6 +51,10 @@ angular.module('zeppelinWebApp', [ templateUrl: 'app/notebook/notebook.html', controller: 'NotebookCtrl' }) + .when('/notebook/:noteId/paragraph?=:paragraphId', { + templateUrl: 'app/notebook/notebook.html', + controller: 'NotebookCtrl' + }) .when('/notebook/:noteId/paragraph/:paragraphId?', { templateUrl: 'app/notebook/notebook.html', controller: 'NotebookCtrl' @@ -58,6 +63,10 @@ angular.module('zeppelinWebApp', [ templateUrl: 'app/interpreter/interpreter.html', controller: 'InterpreterCtrl' }) + .when('/search/:searchTerm', { + templateUrl: 'app/search/result-list.html', + controller: 'SearchResultCtrl' + }) .otherwise({ redirectTo: '/' }); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/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 54903ff..55384ff 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -1,4 +1,5 @@ /* jshint loopfunc: true */ +/* global $: false */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +15,9 @@ */ 'use strict'; -angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, - $rootScope, $http, websocketMsgSrv, baseUrlSrv, - $timeout, SaveAsService) { +angular.module('zeppelinWebApp').controller('NotebookCtrl', + function($scope, $route, $routeParams, $location, $rootScope, $http, + websocketMsgSrv, baseUrlSrv, $timeout, SaveAsService) { $scope.note = null; $scope.showEditor = false; $scope.editorToggled = false; @@ -66,6 +67,26 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro /** Init the new controller */ var initNotebook = function() { websocketMsgSrv.getNotebook($routeParams.noteId); + + var currentRoute = $route.current; + + if (currentRoute) { + + setTimeout( + function() { + var routeParams = currentRoute.params; + var $id = $('#' + routeParams.paragraph + '_container'); + + if ($id.length > 0) { + // adjust for navbar + var top = $id.offset().top - 103; + $('html, body').scrollTo({top: top, left: 0}); + } + + }, + 1000 + ); + } }; initNotebook(); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/app/search/result-list.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/search/result-list.controller.js b/zeppelin-web/src/app/search/result-list.controller.js new file mode 100644 index 0000000..0d55442 --- /dev/null +++ b/zeppelin-web/src/app/search/result-list.controller.js @@ -0,0 +1,119 @@ +/* jshint loopfunc: true */ +/* + * 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'; + +angular + .module('zeppelinWebApp') + .controller('SearchResultCtrl', function($scope, $routeParams, searchService) { + + var results = searchService.search({'q': $routeParams.searchTerm}).query(); + + results.$promise.then(function(result) { + $scope.notes = result.body.map(function(note) { + // redirect to notebook when search result is a notebook itself, + // not a paragraph + if (!/\/paragraph\//.test(note.id)) { + return note; + } + + note.id = note.id.replace('paragraph/', '?paragraph=') + + '&term=' + + $routeParams.searchTerm; + + return note; + }); + }); + + $scope.page = 0; + $scope.allResults = false; + + $scope.highlightSearchResults = function(note) { + return function(_editor) { + function getEditorMode(text) { + var editorModes = { + 'ace/mode/scala': /^%spark/, + 'ace/mode/sql': /^%(\w*\.)?\wql/, + 'ace/mode/markdown': /^%md/, + 'ace/mode/sh': /^%sh/ + }; + + return Object.keys(editorModes).reduce(function(res, mode) { + return editorModes[mode].test(text)? mode : res; + }, 'ace/mode/scala'); + } + + var Range = ace.require('ace/range').Range; + + _editor.setOption('highlightActiveLine', false); + _editor.$blockScrolling = Infinity; + _editor.setReadOnly(true); + _editor.renderer.setShowGutter(false); + _editor.setTheme('ace/theme/chrome'); + _editor.getSession().setMode(getEditorMode(note.text)); + + function getIndeces(term) { + return function(str) { + var indeces = []; + var i = -1; + while((i = str.indexOf(term, i + 1)) >= 0) { + indeces.push(i); + } + return indeces; + }; + } + + var lines = note.snippet + .split('\n') + .map(function(line, row) { + var match = line.match(/<B>(.+?)<\/B>/); + + // return early if nothing to highlight + if (!match) { + return line; + } + + var term = match[1]; + var __line = line + .replace(/<B>/g, '') + .replace(/<\/B>/g, ''); + + var indeces = getIndeces(term)(__line); + + indeces.forEach(function(start) { + var end = start + term.length; + _editor + .getSession() + .addMarker( + new Range(row, start, row, end), + 'search-results-highlight', + 'line' + ); + }); + + return __line; + }); + + // resize editor based on content length + _editor.setOption( + 'maxLines', + lines.reduce(function(len, line) {return len + line.length;}, 0) + ); + + _editor.getSession().setValue(lines.join('\n')); + + }; + }; + +}); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/app/search/result-list.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/search/result-list.html b/zeppelin-web/src/app/search/result-list.html new file mode 100644 index 0000000..2d2b6cf --- /dev/null +++ b/zeppelin-web/src/app/search/result-list.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 ng-controller="SearchResultCtrl" class="searchResults"> + <div class="row"> + <div class="col-sm-8" style="margin: 0 auto; float: none"> + <ul class="search-results"> + <li class="panel panel-default" ng-repeat="note in notes"> + <div class="panel-heading"> + <h4> + <i style="font-size: 10px;" class="icon-doc"></i> + <a class="search-results-header" + href="#/notebook/{{note.id}}"> + {{note.name || 'Note ' + note.id}} + </a> + </h4> + </div> + <div class="panel-body"> + <div + class="search-result" + ui-ace="{ + onLoad: highlightSearchResults(note), + require: ['ace/ext/language_tools'] + }" + ng-model="_" + > + </div> + </div> + </li> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/app/search/search.css ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/search/search.css b/zeppelin-web/src/app/search/search.css new file mode 100644 index 0000000..e89c765 --- /dev/null +++ b/zeppelin-web/src/app/search/search.css @@ -0,0 +1,37 @@ +/* + * 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. + */ + +.search-results { + list-style-type: none; + margin: 10% auto 0; + padding: 0; +} + +.search-result { + height: 200px; +} + +.search-results-header { + text-decoration: none; +} + +.search-results-highlight { + background-color: yellow; + position: absolute; +} + +/* remove error highlighting */ +.search-results .ace_invalid { + background: none !important; +} http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/components/navbar/navbar.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index 81b28de..30e6ac2 100644 --- a/zeppelin-web/src/components/navbar/navbar.controller.js +++ b/zeppelin-web/src/components/navbar/navbar.controller.js @@ -15,8 +15,7 @@ 'use strict'; angular.module('zeppelinWebApp').controller('NavCtrl', function($scope, $rootScope, $routeParams, - notebookListDataFactory, websocketMsgSrv, - arrayOrderingSrv) { + $location, notebookListDataFactory, websocketMsgSrv, arrayOrderingSrv) { /** Current list of notes (ids) */ var vm = this; @@ -35,6 +34,19 @@ angular.module('zeppelinWebApp').controller('NavCtrl', function($scope, $rootSco vm.connected = param; }); + $rootScope.$on('$locationChangeSuccess', function () { + var path = $location.path(); + // hacky solution to clear search bar + // TODO(felizbear): figure out how to make ng-click work in navbar + if (path === '/') { + $scope.searchTerm = ''; + } + }); + + $scope.search = function() { + $location.url(/search/ + $scope.searchTerm); + }; + function loadNotes() { websocketMsgSrv.getNotebookList(); } http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/components/navbar/navbar.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index 82210da..86a8512 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -43,7 +43,34 @@ limitations under the License. <a href="#/interpreter">Interpreter</a> </li> </ul> + + <ul class="nav navbar-nav navbar-right" style="margin-top:10px; margin-right:5px;"> + <li> + <!--TODO(bzz): move to Typeahead https://angular-ui.github.io/bootstrap --> + <form role="search" + style="width: 300px; display: inline-block; margin: 0 10px" + ng-submit="search()"> + <div class="input-group"> + <input + type="text" + ng-model="searchTerm" + ng-disabled="!navbar.connected" + class="form-control" + placeholder="Search in your notebooks" + /> + <span class="input-group-btn"> + <button + type="submit" + class="btn btn-default" + ng-disabled="!navbar.connected" + > + <i class="glyphicon glyphicon-search"></i> + </button> + </span> + </div> + </form> + </li> <li class="server-status"> <i class="fa fa-circle" ng-class="{'server-connected':navbar.connected, 'server-disconnected':!navbar.connected}"></i> <span ng-show="navbar.connected">Connected</span> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/components/searchService/search.service.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/components/searchService/search.service.js b/zeppelin-web/src/components/searchService/search.service.js new file mode 100644 index 0000000..920b5e0 --- /dev/null +++ b/zeppelin-web/src/components/searchService/search.service.js @@ -0,0 +1,29 @@ +/* + * 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'; + +angular.module('zeppelinWebApp').service('searchService', function($resource, baseUrlSrv) { + + this.search = function(term) { + console.log('Searching for: %o', term.q); + if (!term.q) { //TODO(bzz): empty string check + return; + } + var encQuery = window.encodeURIComponent(term.q); + return $resource(baseUrlSrv.getRestApiBase()+'/notebook/search?q='+encQuery, {}, { + query: {method:'GET'} + }); + }; + +}); http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/src/index.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 4bddd98..2b11465 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -49,6 +49,7 @@ limitations under the License. <!-- endbuild --> <!-- build:css(.tmp) styles/main.css --> <link rel="stylesheet" href="app/home/home.css"> + <link rel="stylesheet" href="app/search/search.css"> <link rel="stylesheet" href="app/notebook/notebook.css"> <link rel="stylesheet" href="app/notebook/paragraph/paragraph.css"> <link rel="stylesheet" href="app/interpreter/interpreter.css"> @@ -95,6 +96,7 @@ limitations under the License. <script src="bower_components/angular-animate/angular-animate.js"></script> <script src="bower_components/angular-touch/angular-touch.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> + <script src="bower_components/angular-resource/angular-resource.js"></script> <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> <script src="bower_components/angular-websocket/angular-websocket.min.js"></script> <script src="bower_components/ace-builds/src-noconflict/ace.js"></script> @@ -131,6 +133,7 @@ limitations under the License. <script src="app/notebook/notebook.controller.js"></script> <script src="app/interpreter/interpreter.controller.js"></script> <script src="app/notebook/paragraph/paragraph.controller.js"></script> + <script src="app/search/result-list.controller.js"></script> <script src="components/arrayOrderingSrv/arrayOrdering.service.js"></script> <script src="components/navbar/navbar.controller.js"></script> <script src="components/ngescape/ngescape.directive.js"></script> @@ -147,6 +150,7 @@ limitations under the License. <script src="components/baseUrl/baseUrl.service.js"></script> <script src="components/browser-detect/browserDetect.service.js"></script> <script src="components/saveAs/saveAs.service.js"></script> + <script src="components/searchService/search.service.js"></script> <!-- endbuild --> </body> </html> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-web/test/karma.conf.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js index 1cd2328..34895b4 100644 --- a/zeppelin-web/test/karma.conf.js +++ b/zeppelin-web/test/karma.conf.js @@ -29,6 +29,7 @@ module.exports = function(config) { 'bower_components/angular-animate/angular-animate.js', 'bower_components/angular-touch/angular-touch.js', 'bower_components/angular-route/angular-route.js', + 'bower_components/angular-resource/angular-resource.js', 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', 'bower_components/angular-websocket/angular-websocket.min.js', 'bower_components/ace-builds/src-noconflict/ace.js', http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-zengine/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/pom.xml b/zeppelin-zengine/pom.xml index e9de748..0ef60bf 100644 --- a/zeppelin-zengine/pom.xml +++ b/zeppelin-zengine/pom.xml @@ -124,6 +124,36 @@ </dependency> <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-core</artifactId> + <version>5.3.1</version> + </dependency> + + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-analyzers-common</artifactId> + <version>5.3.1</version> + </dependency> + + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-queryparser</artifactId> + <version>5.3.1</version> + </dependency> + + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-highlighter</artifactId> + <version>5.3.1</version> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.8</version> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/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 93dcec5..6a3074f 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 @@ -20,12 +20,12 @@ package org.apache.zeppelin.notebook; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; -import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.Input; @@ -39,6 +39,7 @@ import org.apache.zeppelin.notebook.utility.IdHashes; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.scheduler.JobListener; +import org.apache.zeppelin.search.SearchService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,40 +50,43 @@ import com.google.gson.Gson; */ public class Note implements Serializable, JobListener { transient Logger logger = LoggerFactory.getLogger(Note.class); - List<Paragraph> paragraphs = new LinkedList<Paragraph>(); - private String name; + private static final long serialVersionUID = 7920699076577612429L; + + List<Paragraph> paragraphs = new LinkedList<>(); + private String name = ""; private String id; - Map<String, List<AngularObject>> angularObjects = new HashMap<String, List<AngularObject>>(); + @SuppressWarnings("rawtypes") + Map<String, List<AngularObject>> angularObjects = new HashMap<>(); private transient NoteInterpreterLoader replLoader; - private transient ZeppelinConfiguration conf; private transient JobListenerFactory jobListenerFactory; private transient NotebookRepo repo; + private transient SearchService index; /** * note configurations. * * - looknfeel - cron */ - private Map<String, Object> config = new HashMap<String, Object>(); + private Map<String, Object> config = new HashMap<>(); /** * note information. * * - cron : cron expression validity. */ - private Map<String, Object> info = new HashMap<String, Object>(); + private Map<String, Object> info = new HashMap<>(); public Note() {} - public Note(NotebookRepo repo, - NoteInterpreterLoader replLoader, - JobListenerFactory jobListenerFactory) { + public Note(NotebookRepo repo, NoteInterpreterLoader replLoader, + JobListenerFactory jlFactory, SearchService noteIndex) { this.repo = repo; this.replLoader = replLoader; - this.jobListenerFactory = jobListenerFactory; + this.jobListenerFactory = jlFactory; + this.index = noteIndex; generateId(); } @@ -130,6 +134,11 @@ public class Note implements Serializable, JobListener { this.repo = repo; } + public void setIndex(SearchService index) { + this.index = index; + } + + @SuppressWarnings("rawtypes") public Map<String, List<AngularObject>> getAngularObjects() { return angularObjects; } @@ -193,14 +202,16 @@ public class Note implements Serializable, JobListener { * Remove paragraph by id. * * @param paragraphId - * @return + * @return a paragraph that was deleted, or <code>null</code> otherwise */ public Paragraph removeParagraph(String paragraphId) { synchronized (paragraphs) { - for (int i = 0; i < paragraphs.size(); i++) { - Paragraph p = paragraphs.get(i); + Iterator<Paragraph> i = paragraphs.iterator(); + while (i.hasNext()) { + Paragraph p = i.next(); if (p.getId().equals(paragraphId)) { - paragraphs.remove(i); + index.deleteIndexDoc(this, p); + i.remove(); return p; } } @@ -293,7 +304,7 @@ public class Note implements Serializable, JobListener { return paragraphs.get(paragraphs.size() - 1); } } - + public List<Map<String, String>> generateParagraphsInfo (){ List<Map<String, String>> paragraphsInfo = new LinkedList<>(); synchronized (paragraphs) { @@ -307,7 +318,7 @@ public class Note implements Serializable, JobListener { } } return paragraphsInfo; - } + } /** * Run all paragraphs sequentially. @@ -357,7 +368,7 @@ public class Note implements Serializable, JobListener { } private void snapshotAngularObjectRegistry() { - angularObjects = new HashMap<String, List<AngularObject>>(); + angularObjects = new HashMap<>(); List<InterpreterSetting> settings = replLoader.getInterpreterSettings(); if (settings == null || settings.size() == 0) { @@ -373,6 +384,7 @@ public class Note implements Serializable, JobListener { public void persist() throws IOException { snapshotAngularObjectRegistry(); + index.updateIndexDoc(this); repo.save(this); } @@ -382,7 +394,7 @@ public class Note implements Serializable, JobListener { public Map<String, Object> getConfig() { if (config == null) { - config = new HashMap<String, Object>(); + config = new HashMap<>(); } return config; } @@ -393,7 +405,7 @@ public class Note implements Serializable, JobListener { public Map<String, Object> getInfo() { if (info == null) { - info = new HashMap<String, Object>(); + info = new HashMap<>(); } return info; } @@ -404,17 +416,10 @@ public class Note implements Serializable, JobListener { @Override public void beforeStatusChange(Job job, Status before, Status after) { - Paragraph p = (Paragraph) job; } @Override public void afterStatusChange(Job job, Status before, Status after) { - Paragraph p = (Paragraph) job; - } - - private static Logger logger() { - Logger logger = LoggerFactory.getLogger(Note.class); - return logger; } @Override http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java ---------------------------------------------------------------------- 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 481f708..c98f2fb 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 @@ -27,6 +27,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; @@ -38,6 +39,7 @@ import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; import org.apache.zeppelin.notebook.repo.NotebookRepo; import org.apache.zeppelin.scheduler.SchedulerFactory; +import org.apache.zeppelin.search.SearchService; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobBuilder; @@ -56,7 +58,10 @@ import org.slf4j.LoggerFactory; */ public class Notebook { Logger logger = LoggerFactory.getLogger(Notebook.class); + + @SuppressWarnings("unused") @Deprecated //TODO(bzz): remove unused private SchedulerFactory schedulerFactory; + private InterpreterFactory replFactory; /** Keep the order. */ Map<String, Note> notes = new LinkedHashMap<String, Note>(); @@ -65,22 +70,45 @@ public class Notebook { private org.quartz.Scheduler quartzSched; private JobListenerFactory jobListenerFactory; private NotebookRepo notebookRepo; + private SearchService notebookIndex; + /** + * Main constructor \w manual Dependency Injection + * + * @param conf + * @param notebookRepo + * @param schedulerFactory + * @param replFactory + * @param jobListenerFactory + * @param notebookIndex - (nullable) for indexing all notebooks on creating. + * + * @throws IOException + * @throws SchedulerException + */ public Notebook(ZeppelinConfiguration conf, NotebookRepo notebookRepo, SchedulerFactory schedulerFactory, - InterpreterFactory replFactory, JobListenerFactory jobListenerFactory) throws IOException, - SchedulerException { + InterpreterFactory replFactory, JobListenerFactory jobListenerFactory, + SearchService notebookIndex) throws IOException, SchedulerException { this.conf = conf; this.notebookRepo = notebookRepo; this.schedulerFactory = schedulerFactory; this.replFactory = replFactory; this.jobListenerFactory = jobListenerFactory; + this.notebookIndex = notebookIndex; quertzSchedFact = new org.quartz.impl.StdSchedulerFactory(); quartzSched = quertzSchedFact.getScheduler(); quartzSched.start(); CronJob.notebook = this; loadAllNotes(); + if (this.notebookIndex != null) { + long start = System.nanoTime(); + logger.info("Notebook indexing started..."); + notebookIndex.addIndexDocs(notes.values()); + logger.info("Notebook indexing finished: {} indexed in {}s", notes.size(), + TimeUnit.NANOSECONDS.toSeconds(start - System.nanoTime())); + } + } /** @@ -90,11 +118,14 @@ public class Notebook { * @throws IOException */ public Note createNote() throws IOException { + Note note; if (conf.getBoolean(ConfVars.ZEPPELIN_NOTEBOOK_AUTO_INTERPRETER_BINDING)) { - return createNote(replFactory.getDefaultInterpreterSettingList()); + note = createNote(replFactory.getDefaultInterpreterSettingList()); } else { - return createNote(null); + note = createNote(null); } + notebookIndex.addIndexDoc(note); + return note; } /** @@ -105,7 +136,7 @@ public class Notebook { */ public Note createNote(List<String> interpreterIds) throws IOException { NoteInterpreterLoader intpLoader = new NoteInterpreterLoader(replFactory); - Note note = new Note(notebookRepo, intpLoader, jobListenerFactory); + Note note = new Note(notebookRepo, intpLoader, jobListenerFactory, notebookIndex); intpLoader.setNoteId(note.id()); synchronized (notes) { notes.put(note.id(), note); @@ -114,6 +145,7 @@ public class Notebook { bindInterpretersToNote(note.id(), interpreterIds); } + notebookIndex.addIndexDoc(note); note.persist(); return note; } @@ -144,6 +176,8 @@ public class Notebook { for (Paragraph p : paragraphs) { newNote.addCloneParagraph(p); } + + notebookIndex.addIndexDoc(newNote); newNote.persist(); return newNote; } @@ -183,9 +217,11 @@ public class Notebook { public void removeNote(String id) { Note note; + synchronized (notes) { note = notes.remove(id); } + notebookIndex.deleteIndexDocs(note); // remove from all interpreter instance's angular object registry for (InterpreterSetting settings : replFactory.get()) { @@ -204,6 +240,7 @@ public class Notebook { } } + @SuppressWarnings("rawtypes") private Note loadNoteFromRepo(String id) { Note note = null; try { @@ -215,20 +252,17 @@ public class Notebook { return null; } - // set NoteInterpreterLoader - NoteInterpreterLoader noteInterpreterLoader = new NoteInterpreterLoader( - replFactory); - note.setReplLoader(noteInterpreterLoader); - noteInterpreterLoader.setNoteId(note.id()); + //Manually inject ALL dependencies, as DI constructor was NOT used + note.setIndex(this.notebookIndex); - // set JobListenerFactory - note.setJobListenerFactory(jobListenerFactory); + NoteInterpreterLoader replLoader = new NoteInterpreterLoader(replFactory); + note.setReplLoader(replLoader); + replLoader.setNoteId(note.id()); - // set notebookRepo + note.setJobListenerFactory(jobListenerFactory); note.setNotebookRepo(notebookRepo); - Map<String, SnapshotAngularObject> angularObjectSnapshot = - new HashMap<String, SnapshotAngularObject>(); + Map<String, SnapshotAngularObject> angularObjectSnapshot = new HashMap<>(); // restore angular object -------------- Date lastUpdatedDate = new Date(0); @@ -246,15 +280,11 @@ public class Notebook { for (String intpGroupName : savedObjects.keySet()) { List<AngularObject> objectList = savedObjects.get(intpGroupName); - for (AngularObject savedObject : objectList) { - SnapshotAngularObject snapshot = angularObjectSnapshot.get(savedObject.getName()); + for (AngularObject object : objectList) { + SnapshotAngularObject snapshot = angularObjectSnapshot.get(object.getName()); if (snapshot == null || snapshot.getLastUpdate().before(lastUpdatedDate)) { - angularObjectSnapshot.put( - savedObject.getName(), - new SnapshotAngularObject( - intpGroupName, - savedObject, - lastUpdatedDate)); + angularObjectSnapshot.put(object.getName(), + new SnapshotAngularObject(intpGroupName, object, lastUpdatedDate)); } } } @@ -310,6 +340,7 @@ public class Notebook { } } + @SuppressWarnings("rawtypes") class SnapshotAngularObject { String intpGroupId; AngularObject angularObject; @@ -344,12 +375,9 @@ public class Notebook { } synchronized (notes) { List<Note> noteList = new ArrayList<Note>(notes.values()); - Collections.sort(noteList, new Comparator() { + Collections.sort(noteList, new Comparator<Note>() { @Override - public int compare(Object one, Object two) { - Note note1 = (Note) one; - Note note2 = (Note) two; - + public int compare(Note note1, Note note2) { String name1 = note1.id(); if (note1.getName() != null) { name1 = note1.getName(); @@ -358,7 +386,6 @@ public class Notebook { if (note2.getName() != null) { name2 = note2.getName(); } - ((Note) one).getName(); return name1.compareTo(name2); } }); @@ -459,6 +486,7 @@ public class Notebook { public void close() { this.notebookRepo.close(); + this.notebookIndex.close(); } } http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/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 ec47efd..433095b 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 @@ -23,7 +23,6 @@ import org.apache.zeppelin.display.Input; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.Interpreter.FormType; import org.apache.zeppelin.interpreter.InterpreterResult.Code; -import org.apache.zeppelin.interpreter.InterpreterResult.Type; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.JobListener; import org.slf4j.Logger; @@ -35,10 +34,10 @@ import java.util.*; /** * Paragraph is a representation of an execution unit. * - * @author Leemoonsoo */ public class Paragraph extends Job implements Serializable, Cloneable { - private static final transient long serialVersionUID = -6328572073497992016L; + private static final long serialVersionUID = -6328572073497992016L; + private transient NoteInterpreterLoader replLoader; private transient Note note; http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/82de508d/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java index 08156c7..a5bf6b3 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java @@ -46,11 +46,11 @@ public class NotebookRepoSync implements NotebookRepo { private List<NotebookRepo> repos = new ArrayList<NotebookRepo>(); /** + * @param noteIndex * @param (conf) * @throws - Exception */ public NotebookRepoSync(ZeppelinConfiguration conf) throws Exception { - config = conf; String allStorageClassNames = conf.getString(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE).trim(); @@ -134,20 +134,18 @@ public class NotebookRepoSync implements NotebookRepo { } /** - * copy new/updated notes from source to destination storage + * Copies new/updated notes from source to destination storage + * * @throws IOException */ void sync(int sourceRepoIndex, int destRepoIndex) throws IOException { LOG.info("Sync started"); - NotebookRepo sourceRepo = getRepo(sourceRepoIndex); - NotebookRepo destRepo = getRepo(destRepoIndex); - List <NoteInfo> sourceNotes = sourceRepo.list(); - List <NoteInfo> destNotes = destRepo.list(); - - Map<String, List<String>> noteIDs = notesCheckDiff(sourceNotes, - sourceRepo, - destNotes, - destRepo); + NotebookRepo srcRepo = getRepo(sourceRepoIndex); + NotebookRepo dstRepo = getRepo(destRepoIndex); + List <NoteInfo> srcNotes = srcRepo.list(); + List <NoteInfo> dstNotes = dstRepo.list(); + + Map<String, List<String>> noteIDs = notesCheckDiff(srcNotes, srcRepo, dstNotes, dstRepo); List<String> pushNoteIDs = noteIDs.get(pushKey); List<String> pullNoteIDs = noteIDs.get(pullKey); if (!pushNoteIDs.isEmpty()) { @@ -155,7 +153,7 @@ public class NotebookRepoSync implements NotebookRepo { for (String id : pushNoteIDs) { LOG.info("ID : " + id); } - pushNotes(pushNoteIDs, sourceRepo, destRepo); + pushNotes(pushNoteIDs, srcRepo, dstRepo); } else { LOG.info("Nothing to push"); } @@ -165,7 +163,7 @@ public class NotebookRepoSync implements NotebookRepo { for (String id : pullNoteIDs) { LOG.info("ID : " + id); } - pushNotes(pullNoteIDs, destRepo, sourceRepo); + pushNotes(pullNoteIDs, dstRepo, srcRepo); } else { LOG.info("Nothing to pull"); }
