Merge remote-tracking branch 'origin/master' into livyInterperter

This commit is contained in:
Prabhjyot Singh 2016-05-16 11:19:00 +05:30
commit f2ea724ab2
15 changed files with 239 additions and 59 deletions

View file

@ -48,7 +48,7 @@ JIRA_USERNAME = os.environ.get("JIRA_USERNAME", "moon")
# ASF JIRA password
JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD", "00000")
GITHUB_BASE = "https://github.com/apache/incubator-zeppelin/pulls"
GITHUB_BASE = "https://github.com/apache/incubator-zeppelin/pull"
GITHUB_API_BASE = "https://api.github.com/repos/apache/incubator-zeppelin"
JIRA_BASE = "https://issues.apache.org/jira/browse"
JIRA_API_BASE = "https://issues.apache.org/jira"

View file

@ -33,7 +33,7 @@ At the "Interpreters" menu, you have to create a new Flink interpreter and provi
</tr>
</table>
For more information about Flink configuration, you can find it [here](https://ci.apache.org/projects/flink/flink-docs-release-0.10/setup/config.html).
For more information about Flink configuration, you can find it [here](https://ci.apache.org/projects/flink/flink-docs-release-1.0/setup/config.html).
## How to test it's working
In example, by using the [Zeppelin notebook](https://www.zeppelinhub.com/viewer/notebooks/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL05GTGFicy96ZXBwZWxpbi1ub3RlYm9va3MvbWFzdGVyL25vdGVib29rcy8yQVFFREs1UEMvbm90ZS5qc29u) is from Till Rohrmann's presentation [Interactive data analysis with Apache Flink](http://www.slideshare.net/tillrohrmann/data-analysis-49806564) for Apache Flink Meetup.

View file

@ -65,6 +65,9 @@ public class LoginRestApi {
JsonResponse response = null;
// ticket set to anonymous for anonymous user. Simplify testing.
Subject currentUser = org.apache.shiro.SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
currentUser.logout();
}
if (!currentUser.isAuthenticated()) {
try {
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
@ -107,6 +110,23 @@ public class LoginRestApi {
LOG.warn(response.toString());
return response.build();
}
@POST
@Path("logout")
public Response logout() {
JsonResponse response;
Subject currentUser = org.apache.shiro.SecurityUtils.getSubject();
currentUser.logout();
Map<String, String> data = new HashMap<>();
data.put("principal", "anonymous");
data.put("roles", "");
data.put("ticket", "anonymous");
response = new JsonResponse(Response.Status.OK, "", data);
LOG.warn(response.toString());
return response.build();
}
}

View file

@ -756,5 +756,31 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
ZeppelinServer.notebook.removeNote(note2.getId());
}
@Test
public void testTitleSearch() throws IOException {
Note note = ZeppelinServer.notebook.createNote();
String jsonRequest = "{\"title\": \"testTitleSearchOfParagraph\", \"text\": \"ThisIsToTestSearchMethodWithTitle \"}";
PostMethod postNotebookText = httpPost("/notebook/" + note.getId() + "/paragraph", jsonRequest);
postNotebookText.releaseConnection();
GetMethod searchNotebook = httpGet("/notebook/search?q='testTitleSearchOfParagraph'");
searchNotebook.addRequestHeader("Origin", "http://localhost");
Map<String, Object> respSearchResult = gson.fromJson(searchNotebook.getResponseBodyAsString(),
new TypeToken<Map<String, Object>>() {
}.getType());
ArrayList searchBody = (ArrayList) respSearchResult.get("body");
int numberOfTitleHits = 0;
for (int i = 0; i < searchBody.size(); i++) {
Map<String, String> searchResult = (Map<String, String>) searchBody.get(i);
if (searchResult.get("header").contains("testTitleSearchOfParagraph")) {
numberOfTitleHits++;
}
}
assertEquals("Paragraph title hits must be at-least one", true, numberOfTitleHits >= 1);
searchNotebook.releaseConnection();
ZeppelinServer.notebook.removeNote(note.getId());
}
}

View file

@ -24,7 +24,7 @@
"angular-elastic": "~2.4.2",
"angular-elastic-input": "~2.2.0",
"angular-xeditable": "0.1.8",
"highlightjs": "~8.4.0",
"highlightjs": "^9.2.0",
"lodash": "~3.9.3",
"angular-filter": "~0.5.4",
"ngtoast": "~2.0.0",

View file

@ -43,7 +43,7 @@ limitations under the License.
You can make beautiful data-driven, interactive, collaborative document with SQL, code and even more!<br>
<div class="row">
<div class="col-md-4">
<div class="col-md-4" ng-if="ticket">
<h4>Notebook
<i ng-class="isReloadingNotes ? 'fa fa-refresh fa-spin' : 'fa fa-refresh'"
ng-style="!isReloadingNotes && {'cursor': 'pointer'}" style="font-size: 13px;"

View file

@ -686,10 +686,24 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
BootstrapDialog.alert({
closable: true,
title: 'Insufficient privileges',
message: data.message
BootstrapDialog.show({
closable: true,
title: 'Insufficient privileges',
message: data.message,
buttons: [{
label: 'Login',
action: function(dialog) {
dialog.close();
angular.element('#loginModal').modal({
show: 'true'
});
}
}, {
label: 'Cancel',
action: function(dialog){
dialog.close();
}
}]
});
});
};

View file

@ -74,12 +74,20 @@ angular
};
}
var lines = note.snippet
var result = '';
if (note.header !== '') {
result = note.header + '\n\n' + note.snippet;
} else {
result = note.snippet;
}
var lines = result
.split('\n')
.map(function(line, row) {
var match = line.match(/<B>(.+?)<\/B>/);
// return early if nothing to highlight
// return early if nothing to highlight
if (!match) {
return line;
}
@ -93,15 +101,31 @@ angular
indeces.forEach(function(start) {
var end = start + term.length;
_editor
.getSession()
.addMarker(
if (note.header !== '' && row === 0) {
_editor
.getSession()
.addMarker(
new Range(row, 0, row, line.length),
'search-results-highlight-header',
'background'
);
_editor
.getSession()
.addMarker(
new Range(row, start, row, end),
'search-results-highlight',
'line'
);
} else {
_editor
.getSession()
.addMarker(
new Range(row, start, row, end),
'search-results-highlight',
'line'
);
}
});
return __line;
});

View file

@ -31,6 +31,11 @@
position: absolute;
}
.search-results-highlight-header {
background-color: #e6f2ff;
position: absolute;
}
/* remove error highlighting */
.search-results .ace_invalid {
background: none !important;

View file

@ -33,6 +33,7 @@ angular.module('zeppelinWebApp').controller('LoginCtrl',
$rootScope.ticket = response.data.body;
angular.element('#loginModal').modal('toggle');
$rootScope.$broadcast('loginSuccess', true);
$rootScope.userName = $scope.loginParams.userName;
}, function errorCallback(errorResponse) {
$scope.loginParams.errorText = 'The username and password that you entered don\'t match.';
});

View file

@ -14,8 +14,8 @@
'use strict';
angular.module('zeppelinWebApp').controller('NavCtrl', function($scope, $rootScope, $routeParams,
$location, notebookListDataFactory, websocketMsgSrv, arrayOrderingSrv) {
angular.module('zeppelinWebApp').controller('NavCtrl', function($scope, $rootScope, $http, $routeParams,
$location, notebookListDataFactory, baseUrlSrv, websocketMsgSrv, arrayOrderingSrv) {
/** Current list of notes (ids) */
$scope.showLoginWindow = function() {
@ -71,6 +71,26 @@ angular.module('zeppelinWebApp').controller('NavCtrl', function($scope, $rootSco
loadNotes();
});
$scope.logout = function() {
$http.post(baseUrlSrv.getRestApiBase()+'/login/logout')
.success(function(data, status, headers, config) {
$rootScope.userName = '';
$rootScope.ticket.principal = '';
$rootScope.ticket.ticket = '';
$rootScope.ticket.roles = '';
BootstrapDialog.show({
message: 'Logout Success'
});
setTimeout(function() {
window.location = '#';
window.location.reload();
}, 1000);
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
$scope.search = function() {
$location.url(/search/ + $scope.searchTerm);
};

View file

@ -2,9 +2,7 @@
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.
@ -37,7 +35,7 @@ limitations under the License.
</div>
<div class="collapse navbar-collapse" ng-controller="NavCtrl as navbar">
<ul class="nav navbar-nav">
<ul class="nav navbar-nav" ng-if="ticket">
<li class="dropdown" dropdown>
<a href="#" class="dropdown-toggle" dropdown-toggle>Notebook <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
@ -63,7 +61,7 @@ limitations under the License.
<ul class="nav navbar-nav navbar-right" style="margin-top:10px; margin-right:5px;">
<li>
<li ng-if="ticket">
<!--TODO(bzz): move to Typeahead https://angular-ui.github.io/bootstrap -->
<form role="search"
style="width: 300px; display: inline-block; margin: 0 10px"
@ -96,6 +94,9 @@ limitations under the License.
<li ng-if="!ticket">
<button class="btn btn-default" data-toggle="modal" data-target="#loginModal" ng-click="showLoginWindow()" style="margin-left: 10px">Login</button>
</li>
<li ng-show="ticket.principal && ticket.principal!='anonymous'" style="left: 5px;">
<button class="btn btn-default" ng-click="logout()" tooltip-placement="bottom" tooltip="logout">Logout</button>
</li>
</ul>
</div>
</div>

View file

@ -60,10 +60,24 @@ angular.module('zeppelinWebApp').factory('websocketEvents', function($rootScope,
} else if (op === 'NOTES_INFO') {
$rootScope.$broadcast('setNoteMenu', data.notes);
} else if (op === 'AUTH_INFO') {
BootstrapDialog.alert({
closable: true,
title: 'Insufficient privileges',
message: data.info.toString()
BootstrapDialog.show({
closable: true,
title: 'Insufficient privileges',
message: data.info.toString(),
buttons: [{
label: 'Login',
action: function(dialog) {
dialog.close();
angular.element('#loginModal').modal({
show: 'true'
});
}
}, {
label: 'Cancel',
action: function(dialog){
dialog.close();
}
}]
});
} else if (op === 'PARAGRAPH') {
$rootScope.$broadcast('updateParagraph', data);

View file

@ -37,6 +37,7 @@ import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
@ -69,7 +70,8 @@ import com.google.common.collect.Lists;
public class LuceneSearch implements SearchService {
private static final Logger LOG = LoggerFactory.getLogger(LuceneSearch.class);
private static final String SEARCH_FIELD = "contents";
private static final String SEARCH_FIELD_TEXT = "contents";
private static final String SEARCH_FIELD_TITLE = "header";
static final String PARAGRAPH = "paragraph";
static final String ID_FIELD = "id";
@ -85,7 +87,7 @@ public class LuceneSearch implements SearchService {
try {
writer = new IndexWriter(ramDirectory, iwc);
} catch (IOException e) {
LOG.error("Failed to reate new IndexWriter", e);
LOG.error("Failed to create new IndexWriter", e);
}
}
@ -102,10 +104,12 @@ public class LuceneSearch implements SearchService {
try (IndexReader indexReader = DirectoryReader.open(ramDirectory)) {
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser(SEARCH_FIELD, analyzer);
MultiFieldQueryParser parser = new MultiFieldQueryParser(
new String[] {SEARCH_FIELD_TEXT, SEARCH_FIELD_TITLE},
analyzer);
Query query = parser.parse(queryStr);
LOG.debug("Searching for: " + query.toString(SEARCH_FIELD));
LOG.debug("Searching for: " + query.toString(SEARCH_FIELD_TEXT));
SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter();
Highlighter highlighter = new Highlighter(htmlFormatter, new QueryScorer(query));
@ -139,20 +143,33 @@ public class LuceneSearch implements SearchService {
LOG.debug(" Title: {}", doc.get("title"));
}
String text = doc.get(SEARCH_FIELD);
TokenStream tokenStream = TokenSources.getTokenStream(searcher.getIndexReader(), id,
SEARCH_FIELD, analyzer);
TextFragment[] frag = highlighter.getBestTextFragments(tokenStream, text, true, 3);
LOG.debug(" {} fragments found for query '{}'", frag.length, query);
for (int j = 0; j < frag.length; j++) {
if ((frag[j] != null) && (frag[j].getScore() > 0)) {
LOG.debug(" Fragment: {}", frag[j].toString());
}
}
String fragment = (frag != null && frag.length > 0) ? frag[0].toString() : "";
String text = doc.get(SEARCH_FIELD_TEXT);
String header = doc.get(SEARCH_FIELD_TITLE);
String fragment = "";
if (text != null) {
TokenStream tokenStream = TokenSources.getTokenStream(searcher.getIndexReader(), id,
SEARCH_FIELD_TEXT, analyzer);
TextFragment[] frag = highlighter.getBestTextFragments(tokenStream, text, true, 3);
LOG.debug(" {} fragments found for query '{}'", frag.length, query);
for (int j = 0; j < frag.length; j++) {
if ((frag[j] != null) && (frag[j].getScore() > 0)) {
LOG.debug(" Fragment: {}", frag[j].toString());
}
}
fragment = (frag != null && frag.length > 0) ? frag[0].toString() : "";
}
if (header != null) {
TokenStream tokenTitle = TokenSources.getTokenStream(searcher.getIndexReader(), id,
SEARCH_FIELD_TITLE, analyzer);
TextFragment[] frgTitle = highlighter.getBestTextFragments(tokenTitle, header, true, 3);
header = (frgTitle != null && frgTitle.length > 0) ? frgTitle[0].toString() : "";
} else {
header = "";
}
matchingParagraphs.add(ImmutableMap.of("id", path, // <noteId>/paragraph/<paragraphId>
"name", title, "snippet", fragment, "text", text));
"name", title, "snippet", fragment, "text", text, "header", header));
} else {
LOG.info("{}. No {} for this document", i + 1, ID_FIELD);
}
@ -252,11 +269,14 @@ public class LuceneSearch implements SearchService {
doc.add(new StringField("title", noteName, Field.Store.YES));
if (null != p) {
doc.add(new TextField(SEARCH_FIELD, p.getText(), Field.Store.YES));
doc.add(new TextField(SEARCH_FIELD_TEXT, p.getText(), Field.Store.YES));
if (p.getTitle() != null) {
doc.add(new TextField(SEARCH_FIELD_TITLE, p.getTitle(), Field.Store.YES));
}
Date date = p.getDateStarted() != null ? p.getDateStarted() : p.getDateCreated();
doc.add(new LongField("modified", date.getTime(), Field.Store.NO));
} else {
doc.add(new TextField(SEARCH_FIELD, noteName, Field.Store.YES));
doc.add(new TextField(SEARCH_FIELD_TEXT, noteName, Field.Store.YES));
}
return doc;
}

View file

@ -65,8 +65,8 @@ public class LuceneSearchTest {
@Test public void canIndexNotebook() {
//give
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraph("Notebook2", "not test");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraph("Notebook2", "not test");
List<Note> notebook = Arrays.asList(note1, note2);
//when
@ -75,8 +75,8 @@ public class LuceneSearchTest {
@Test public void canIndexAndQuery() {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
@ -91,8 +91,8 @@ public class LuceneSearchTest {
@Test public void canIndexAndQueryByNotebookName() {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
@ -104,9 +104,31 @@ public class LuceneSearchTest {
assertThat(results.get(0)).containsEntry("id", note1.getId());
}
@Test
public void canIndexAndQueryByParagraphTitle() {
//given
Note note1 = newNoteWithParagraph("Notebook1", "test", "testingTitleSearch");
Note note2 = newNoteWithParagraph("Notebook2", "not test", "notTestingTitleSearch");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
List<Map<String, String>> results = notebookIndex.query("testingTitleSearch");
//then
assertThat(results).isNotEmpty();
assertThat(results.size()).isAtLeast(1);
int TitleHits = 0;
for (Map<String, String> res : results) {
if (res.get("header").contains("testingTitleSearch")) {
TitleHits++;
}
}
assertThat(TitleHits).isAtLeast(1);
}
@Test public void indexKeyContract() throws IOException {
//give
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note1 = newNoteWithParagraph("Notebook1", "test");
//when
notebookIndex.addIndexDoc(note1);
//then
@ -129,8 +151,8 @@ public class LuceneSearchTest {
@Test public void canIndexAndReIndex() throws IOException {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
@ -155,8 +177,8 @@ public class LuceneSearchTest {
@Test public void canDeleteFromIndex() throws IOException {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("Notebook2")).isNotEmpty();
@ -174,8 +196,8 @@ public class LuceneSearchTest {
@Test public void indexParagraphUpdatedOnNoteSave() throws IOException {
//given: total 2 notebooks, 3 paragraphs
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("test").size()).isEqualTo(3);
@ -199,8 +221,8 @@ public class LuceneSearchTest {
@Test public void indexNoteNameUpdatedOnNoteSave() throws IOException {
//given: total 2 notebooks, 3 paragraphs
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
Note note1 = newNoteWithParagraph("Notebook1", "test");
Note note2 = newNoteWithParagraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("test").size()).isEqualTo(3);
@ -226,17 +248,23 @@ public class LuceneSearchTest {
* @param parText text of the paragraph
* @return Note
*/
private Note newNoteWithParapgraph(String noteName, String parText) {
private Note newNoteWithParagraph(String noteName, String parText) {
Note note1 = newNote(noteName);
addParagraphWithText(note1, parText);
return note1;
}
private Note newNoteWithParagraph(String noteName, String parText,String title) {
Note note = newNote(noteName);
addParagraphWithTextAndTitle(note, parText, title);
return note;
}
/**
* Creates a new Note \w given name,
* adds N paragraphs \w given texts
*/
private Note newNoteWithParapgraphs(String noteName, String... parTexts) {
private Note newNoteWithParagraphs(String noteName, String... parTexts) {
Note note1 = newNote(noteName);
for (String parText : parTexts) {
addParagraphWithText(note1, parText);
@ -250,6 +278,13 @@ public class LuceneSearchTest {
return p;
}
private Paragraph addParagraphWithTextAndTitle(Note note, String text, String title) {
Paragraph p = note.addParagraph();
p.setText(text);
p.setTitle(title);
return p;
}
private Note newNote(String name) {
Note note = new Note(notebookRepoMock, replLoaderMock, null, notebookIndex);
note.setName(name);