diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md index 7393c5a2a87..fade802c5b4 100644 --- a/docs/rest-api/rest-notebook.md +++ b/docs/rest-api/rest-notebook.md @@ -556,4 +556,33 @@ limitations under the License.
{"status":"OK","body":"* * * * * ?"}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Full-text search through the paragraphs in all notebooks
Description```GET``` request will return list of matching paragraphs +
URL```http://[zeppelin-server]:[zeppelin-port]/api/notebook/search?q=[query]```
Success code200
Fail code 500
Sample JSON response
{"status":"OK", body: [{"id":"/paragraph/", "name":"Notebook Name", "snippet":"", "text":""}]}
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE index 1cce5dcec9c..52ce3ebc7f2 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 ======================================================================== 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 4c8c70a4af6..c803d78f26f 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 @@ * and saving/loading jobs from disk. * Changing/adding/deleting non transitive field name need consideration of that. * - * @author Leemoonsoo */ public abstract class Job { /** diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 0563374da5a..e77ee6ca38d 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -197,6 +197,22 @@ 2.2.1 + + com.sun.jersey + jersey-servlet + 1.13 + + + + javax.ws.rs + javax.ws.rs-api + 2.0-m10 + + + + org.scala-lang + scala-library + org.seleniumhq.selenium @@ -240,23 +256,6 @@ test - - com.sun.jersey - jersey-servlet - 1.13 - - - - javax.ws.rs - javax.ws.rs-api - 2.0-m10 - - - - org.scala-lang - scala-library - - org.scalatest scalatest_2.10 @@ -268,6 +267,7 @@ org.mockito mockito-all 1.9.0 + test 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 d9e7cf70d0c..fb4e99473a7 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.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 @@ @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 NotebookRestApi(Notebook notebook, NotebookServer notebookServer) { public Response bind(@PathParam("noteId") String noteId, String req) throws IOException { List settingIdList = gson.fromJson(req, new TypeToken>(){}.getType()); notebook.bindInterpretersToNote(noteId, settingIdList); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -114,14 +125,14 @@ public Response bind(@PathParam("noteId") String noteId) { ); } } - return new JsonResponse(Status.OK, "", settingList).build(); + return new JsonResponse<>(Status.OK, "", settingList).build(); } @GET @Path("/") public Response getNotebookList() throws IOException { List> notesInfo = notebookServer.generateNotebooksInfo(); - return new JsonResponse(Status.OK, "", notesInfo ).build(); + return new JsonResponse<>(Status.OK, "", notesInfo ).build(); } @GET @@ -129,10 +140,10 @@ public Response getNotebookList() throws IOException { 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 Response getNotebook(@PathParam("notebookId") String notebookId) throws I @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 Response createNote(String message) throws IOException { 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 Response createNote(String message) throws IOException { @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 Response deleteNote(@PathParam("notebookId") String notebookId) throws IO } } notebookServer.broadcastNoteList(); - return new JsonResponse(Status.OK, "").build(); + return new JsonResponse<>(Status.OK, "").build(); } /** @@ -198,14 +209,14 @@ public Response deleteNote(@PathParam("notebookId") String notebookId) throws IO @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 Response cloneNote(@PathParam("notebookId") String notebookId, String mes @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 Response runNoteJobs(@PathParam("notebookId") String notebookId) throws @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 Response stopNoteJobs(@PathParam("notebookId") String notebookId) throws p.abort(); } } - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -262,19 +273,21 @@ public Response stopNoteJobs(@PathParam("notebookId") String notebookId) throws @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 Response runParagraph(@PathParam("notebookId") String notebookId, @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 Response runParagraph(@PathParam("notebookId") String notebookId, } note.run(paragraph.getId()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -322,18 +335,18 @@ public Response runParagraph(@PathParam("notebookId") String notebookId, 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 Response stopParagraph(@PathParam("notebookId") String notebookId, @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 config = note.getConfig(); @@ -365,7 +378,7 @@ public Response registerCronJob(@PathParam("notebookId") String notebookId, Stri note.setConfig(config); notebook.refreshCron(note.id()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -378,11 +391,11 @@ public Response registerCronJob(@PathParam("notebookId") String notebookId, Stri @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 config = note.getConfig(); @@ -390,7 +403,7 @@ public Response removeCronJob(@PathParam("notebookId") String notebookId) throws note.setConfig(config); notebook.refreshCron(note.id()); - return new JsonResponse(Status.OK).build(); + return new JsonResponse<>(Status.OK).build(); } /** @@ -403,13 +416,26 @@ public Response removeCronJob(@PathParam("notebookId") String notebookId) throws @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> notebooksFound = notebookIndex.query(queryTerm); + LOG.info("{} notbooks found", notebooksFound.size()); + return new JsonResponse<>(Status.OK, notebooksFound).build(); + } + } 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 367f9238f46..9a0b883e79b 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 @@ /** * Zeppelin root rest api endpoint. * - * @author anthonycorbacho * @since 0.3.4 */ @Path("/") 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 7286b355a5c..fd115ee18cb 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.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 Set getSingletons() { 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); 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 a010e581ceb..554f68cf073 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 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> noteSocketMap = new HashMap<>(); final Queue 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 boolean checkOrigin(HttpServletRequest request, String origin) { } catch (URISyntaxException e) { e.printStackTrace(); } - return false; } 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 caac5a08fe3..2bca9c08593 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.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 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 { diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 4e5c3534066..ee9ea466f2e 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", diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 64d43bb3c59..92e7345db05 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: '/' }); diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 54903ff9b58..55384ff3b0a 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(); 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 00000000000..0d55442e525 --- /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>/); + + // return early if nothing to highlight + if (!match) { + return line; + } + + var term = match[1]; + var __line = line + .replace(//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')); + + }; + }; + +}); 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 00000000000..2d2b6cfc510 --- /dev/null +++ b/zeppelin-web/src/app/search/result-list.html @@ -0,0 +1,42 @@ + +
+
+
+
+
+
diff --git a/zeppelin-web/src/app/search/search.css b/zeppelin-web/src/app/search/search.css new file mode 100644 index 00000000000..e89c765f393 --- /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; +} diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index 81b28de6c69..30e6ac27892 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(); } diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index 82210da1855..86a85122add 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -43,7 +43,34 @@ Interpreter + +