Merge branch 'master' into ZEPPELIN-732-up

This commit is contained in:
Lee moon soo 2016-04-30 09:31:38 -07:00
commit 7451479d71
12 changed files with 234 additions and 49 deletions

2
.gitignore vendored
View file

@ -39,6 +39,8 @@ zeppelin-web/bower_components
**nbproject/
**node/
#R
/r/lib/
# project level
/logs/

View file

@ -50,6 +50,7 @@ import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.GsonBuilder;
@ -127,9 +128,29 @@ public class NotebookRestApi {
return new JsonResponse<>(Status.FORBIDDEN, ownerPermissionError(userAndRoles,
notebookAuthorization.getOwners(noteId))).build();
}
notebookAuthorization.setOwners(noteId, permMap.get("owners"));
notebookAuthorization.setReaders(noteId, permMap.get("readers"));
notebookAuthorization.setWriters(noteId, permMap.get("writers"));
HashSet readers = permMap.get("readers");
HashSet owners = permMap.get("owners");
HashSet writers = permMap.get("writers");
// Set readers, if writers and owners is empty -> set to user requesting the change
if (readers != null && !readers.isEmpty()) {
if (writers.isEmpty()) {
writers = Sets.newHashSet(SecurityUtils.getPrincipal());
}
if (owners.isEmpty()) {
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
}
}
// Set writers, if owners is empty -> set to user requesting the change
if ( writers != null && !writers.isEmpty()) {
if (owners.isEmpty()) {
owners = Sets.newHashSet(SecurityUtils.getPrincipal());
}
}
notebookAuthorization.setReaders(noteId, readers);
notebookAuthorization.setWriters(noteId, writers);
notebookAuthorization.setOwners(noteId, owners);
LOG.debug("After set permissions {} {} {}",
notebookAuthorization.getOwners(noteId),
notebookAuthorization.getReaders(noteId),

View file

@ -207,7 +207,7 @@ public class ZeppelinServer extends Application {
}
private static void setupNotebookServer(WebAppContext webapp,
ZeppelinConfiguration conf) {
ZeppelinConfiguration conf) {
notebookWsServer = new NotebookServer();
String maxTextMessageSize = conf.getWebsocketMaxTextMessageSize();
final ServletHolder servletHolder = new ServletHolder(notebookWsServer);
@ -217,9 +217,6 @@ public class ZeppelinServer extends Application {
ServletContextHandler.SESSIONS);
webapp.addServlet(servletHolder, "/ws/*");
webapp.addFilter(new FilterHolder(CorsFilter.class), "/*",
EnumSet.allOf(DispatcherType.class));
}
private static SslContextFactory getSslContextFactory(ZeppelinConfiguration conf) {
@ -266,7 +263,7 @@ public class ZeppelinServer extends Application {
}
private static WebAppContext setupWebAppContext(ContextHandlerCollection contexts,
ZeppelinConfiguration conf) {
ZeppelinConfiguration conf) {
WebAppContext webApp = new WebAppContext();
webApp.setContextPath(conf.getServerContextPath());

View file

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.rest;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.NotebookAuthorizationInfoSaving;
import org.apache.zeppelin.server.ZeppelinServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Zeppelin notebook rest api tests
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class NotebookRestApiTest extends AbstractTestRestApi {
Gson gson = new Gson();
@BeforeClass
public static void init() throws Exception {
AbstractTestRestApi.startUp();
}
@AfterClass
public static void destroy() throws Exception {
AbstractTestRestApi.shutDown();
}
@Test
public void testPermissions() throws IOException {
Note note1 = ZeppelinServer.notebook.createNote();
// Set only readers
String jsonRequest = "{\"readers\":[\"admin-team\"],\"owners\":[]," +
"\"writers\":[]}";
PutMethod put = httpPut("/notebook/" + note1.getId() + "/permissions/", jsonRequest);
LOG.info("testPermissions response\n" + put.getResponseBodyAsString());
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
GetMethod get = httpGet("/notebook/" + note1.getId() + "/permissions/");
assertThat(get, isAllowed());
Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
Map<String, Set<String>> authInfo = (Map<String, Set<String>>) resp.get("body");
// Check that both owners and writers is set to the princpal if empty
assertEquals(authInfo.get("readers"), Lists.newArrayList("admin-team"));
assertEquals(authInfo.get("owners"), Lists.newArrayList("anonymous"));
assertEquals(authInfo.get("writers"), Lists.newArrayList("anonymous"));
get.releaseConnection();
Note note2 = ZeppelinServer.notebook.createNote();
// Set only writers
jsonRequest = "{\"readers\":[],\"owners\":[]," +
"\"writers\":[\"admin-team\"]}";
put = httpPut("/notebook/" + note2.getId() + "/permissions/", jsonRequest);
assertThat("test update method:", put, isAllowed());
put.releaseConnection();
get = httpGet("/notebook/" + note2.getId() + "/permissions/");
assertThat(get, isAllowed());
resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
authInfo = (Map<String, Set<String>>) resp.get("body");
// Check that owners is set to the princpal if empty
assertEquals(authInfo.get("owners"), Lists.newArrayList("anonymous"));
assertEquals(authInfo.get("writers"), Lists.newArrayList("admin-team"));
get.releaseConnection();
// Test clear permissions
jsonRequest = "{\"readers\":[],\"owners\":[],\"writers\":[]}";
put = httpPut("/notebook/" + note2.getId() + "/permissions/", jsonRequest);
put.releaseConnection();
get = httpGet("/notebook/" + note2.getId() + "/permissions/");
assertThat(get, isAllowed());
resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {
}.getType());
authInfo = (Map<String, Set<String>>) resp.get("body");
assertEquals(authInfo.get("readers"), Lists.newArrayList());
assertEquals(authInfo.get("writers"), Lists.newArrayList());
assertEquals(authInfo.get("owners"), Lists.newArrayList());
get.releaseConnection();
//cleanup
ZeppelinServer.notebook.removeNote(note1.getId());
ZeppelinServer.notebook.removeNote(note2.getId());
}
}

View file

@ -354,6 +354,11 @@ module.exports = function (grunt) {
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: ['app/**/*.html', 'components/**/*.html']
}, {
expand: true,
cwd: 'bower_components/datatables/media/images',
src: '{,*/}*.{png,jpg,jpeg,gif}',
dest: '<%= yeoman.dist %>/images'
}, {
expand: true,
cwd: '.tmp/images',

View file

@ -30,7 +30,9 @@
"ngtoast": "~2.0.0",
"ng-focus-if": "~1.0.2",
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
"floatThead": "~1.3.2"
"floatThead": "~1.3.2",
"datatables.net-bs": "~1.10.11",
"datatables.net-buttons-bs": "~1.1.2"
},
"devDependencies": {
"angular-mocks": "1.5.0"

View file

@ -14,6 +14,7 @@ limitations under the License.
<div
id="p{{paragraph.id}}_resize"
ng-if="!paragraph.config.helium.activeApp"
style='padding-bottom: 5px;'
resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{getResultType()}}"}'
resizable on-resize="resizeParagraph(width, height);">
<div ng-include src="'app/notebook/paragraph/paragraph-graph.html'"></div>

View file

@ -1324,40 +1324,55 @@ angular.module('zeppelinWebApp')
return '&#'+i.charCodeAt(0)+';';
});
}
html += ' <td>'+formatTableContent(v)+'</td>';
html += ' <td>'+formatTableContent(v)+'</td>';
}
html += ' </tr>';
}
html += ' </tbody>';
html += '</table>';
angular.element('#p' + $scope.paragraph.id + '_table').html(html);
var tableDomEl = angular.element('#p' + $scope.paragraph.id + '_table');
tableDomEl.html(html);
var oTable = tableDomEl.children(1).DataTable({
paging: false,
info: false,
autoWidth: false,
lengthChange: false,
searching: false,
dom: '<>'
});
if ($scope.paragraph.result.msgTable.length > 10000) {
angular.element('#p' + $scope.paragraph.id + '_table').css('overflow', 'scroll');
// set table height
var height = $scope.paragraph.config.graph.height;
angular.element('#p' + $scope.paragraph.id + '_table').css('height', height);
tableDomEl.css({
'overflow': 'scroll',
'height': $scope.paragraph.config.graph.height
});
} else {
var dataTable = angular.element('#p' + $scope.paragraph.id + '_table .table');
dataTable.floatThead({
scrollContainer: function (dataTable) {
return angular.element('#p' + $scope.paragraph.id + '_table');
scrollContainer: function(dataTable) {
return tableDomEl;
}
});
angular.element('#p' + $scope.paragraph.id + '_table .table').on('remove', function () {
angular.element('#p' + $scope.paragraph.id + '_table .table').floatThead('destroy');
dataTable.on('remove', function () {
dataTable.floatThead('destroy');
});
angular.element('#p' + $scope.paragraph.id + '_table').css('position', 'relative');
angular.element('#p' + $scope.paragraph.id + '_table').css('height', '100%');
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar('destroy');
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar();
tableDomEl.css({
'position': 'relative',
'height': '100%'
});
tableDomEl.perfectScrollbar('destroy')
.perfectScrollbar({minScrollbarLength: 20});
angular.element('.ps-scrollbar-y-rail').css('z-index', '1002');
// set table height
var psHeight = $scope.paragraph.config.graph.height;
angular.element('#p' + $scope.paragraph.id + '_table').css('height', psHeight);
angular.element('#p' + $scope.paragraph.id + '_table').perfectScrollbar('update');
tableDomEl.css('height', psHeight);
tableDomEl.perfectScrollbar('update');
}
};

View file

@ -52,6 +52,21 @@
width: 100%;
}
table.dataTable {
margin-top: 0px !important;
margin-bottom: 6px !important;
}
table.dataTable.table-condensed > thead > tr > th {
padding-right: 28px;
}
table.dataTable.table-condensed .sorting:after,
table.dataTable.table-condensed .sorting_asc:after,
table.dataTable.table-condensed .sorting_desc:after {
right: 12px;
}
.graphContainer {
position: relative;
margin-bottom: 5px;

View file

@ -44,6 +44,8 @@ limitations under the License.
<link rel="stylesheet" href="bower_components/highlightjs/styles/github.css" />
<link rel="stylesheet" href="bower_components/ngtoast/dist/ngToast.css" />
<link rel="stylesheet" href="bower_components/bootstrap3-dialog/dist/css/bootstrap-dialog.min.css" />
<link rel="stylesheet" href="bower_components/datatables.net-bs/css/dataTables.bootstrap.css" />
<link rel="stylesheet" href="bower_components/datatables.net-buttons-bs/css/buttons.bootstrap.css" />
<!-- endbower -->
<link rel="stylesheet" href="bower_components/jquery-ui/themes/base/all.css" />
<!-- endbuild -->
@ -132,6 +134,14 @@ limitations under the License.
<script src="bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script>
<script src="bower_components/floatThead/dist/jquery.floatThead.js"></script>
<script src="bower_components/floatThead/dist/jquery.floatThead.min.js"></script>
<script src="bower_components/datatables.net/js/jquery.dataTables.js"></script>
<script src="bower_components/datatables.net-bs/js/dataTables.bootstrap.js"></script>
<script src="bower_components/datatables.net-buttons/js/dataTables.buttons.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.colVis.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.flash.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.html5.js"></script>
<script src="bower_components/datatables.net-buttons/js/buttons.print.js"></script>
<script src="bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.js"></script>
<!-- endbower -->
<!-- endbuild -->
<!-- build:js({.tmp,src}) scripts/scripts.js -->

View file

@ -110,16 +110,10 @@ public class NotebookAuthorization {
noteAuthInfo.put("owners", new LinkedHashSet(entities));
noteAuthInfo.put("readers", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet());
authInfo.put(noteId, noteAuthInfo);
} else {
Set<String> existingEntities = noteAuthInfo.get("owners");
if (existingEntities == null) {
noteAuthInfo.put("owners", new LinkedHashSet(entities));
} else {
existingEntities.clear();
existingEntities.addAll(entities);
}
noteAuthInfo.put("owners", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}
@ -130,16 +124,10 @@ public class NotebookAuthorization {
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet(entities));
noteAuthInfo.put("writers", new LinkedHashSet());
authInfo.put(noteId, noteAuthInfo);
} else {
Set<String> existingEntities = noteAuthInfo.get("readers");
if (existingEntities == null) {
noteAuthInfo.put("readers", new LinkedHashSet(entities));
} else {
existingEntities.clear();
existingEntities.addAll(entities);
}
noteAuthInfo.put("readers", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}
@ -150,16 +138,10 @@ public class NotebookAuthorization {
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet(entities));
authInfo.put(noteId, noteAuthInfo);
} else {
Set<String> existingEntities = noteAuthInfo.get("writers");
if (existingEntities == null) {
noteAuthInfo.put("writers", new LinkedHashSet(entities));
} else {
existingEntities.clear();
existingEntities.addAll(entities);
}
noteAuthInfo.put("writers", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}

View file

@ -29,6 +29,7 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.collect.Sets;
import org.apache.commons.io.FileUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
@ -486,6 +487,13 @@ public class NotebookTest implements JobListenerFactory{
assertEquals(notebookAuthorization.isWriter(note.id(),
new HashSet<String>(Arrays.asList("user1"))), true);
// Test clearing of permssions
notebookAuthorization.setReaders(note.id(), Sets.<String>newHashSet());
assertEquals(notebookAuthorization.isReader(note.id(),
new HashSet<String>(Arrays.asList("user2"))), true);
assertEquals(notebookAuthorization.isReader(note.id(),
new HashSet<String>(Arrays.asList("user3"))), true);
notebook.removeNote(note.id());
}