Repository: zeppelin Updated Branches: refs/heads/master 9efbcd1b6 -> 1d0028bfe
[ZEPPELIN-1237] Auto-suggestion of notebook permissions should list roles as well ### What is this PR for? Auto-suggestion of notebook permissions should list roles as well ### What type of PR is it? [Improvement] ### Todos * [x] - Fix test case (selenium) * [x] - select2 in bower.json ### What is the Jira issue? * [ZEPPELIN-1237](https://issues.apache.org/jira/browse/ZEPPELIN-1237) ### How should this be tested? Search for group/roles in notebook permission, it should get listed ### Screenshots (if appropriate)  ### Questions: * Does the licenses files need update? n/a * Is there breaking changes for older versions? n/a * Does this needs documentation? n/a Author: Prabhjyot Singh <[email protected]> Closes #1236 from prabhjyotsingh/ZEPPELIN-1237 and squashes the following commits: b944dc2 [Prabhjyot Singh] Merge remote-tracking branch 'origin/master' into ZEPPELIN-1237 17e17a9 [Prabhjyot Singh] implement @r-kamath feedback 0793c10 [Prabhjyot Singh] Auto-suggestion of notebook permissions should list group as well Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/1d0028bf Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/1d0028bf Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/1d0028bf Branch: refs/heads/master Commit: 1d0028bfec0594efc97a0652acffbde76eb75fa8 Parents: 9efbcd1 Author: Prabhjyot Singh <[email protected]> Authored: Sat Jul 30 19:48:44 2016 +0530 Committer: Prabhjyot Singh <[email protected]> Committed: Thu Aug 4 11:42:39 2016 +0530 ---------------------------------------------------------------------- .../org/apache/zeppelin/rest/GetUserList.java | 19 ++ .../apache/zeppelin/rest/SecurityRestApi.java | 30 ++- .../zeppelin/integration/AuthenticationIT.java | 12 +- .../zeppelin/rest/SecurityRestApiTest.java | 4 +- zeppelin-web/bower.json | 3 +- zeppelin-web/src/app/app.js | 7 + .../src/app/notebook/notebook.controller.js | 270 +++++-------------- zeppelin-web/src/app/notebook/notebook.css | 50 +--- zeppelin-web/src/app/notebook/notebook.html | 55 +--- zeppelin-web/src/index.html | 2 + zeppelin-web/test/karma.conf.js | 1 + 11 files changed, 138 insertions(+), 315 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java index 2727fb4..f1a895c 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java @@ -67,6 +67,25 @@ public class GetUserList { return userList; } + + /*** + * Get user roles from shiro.ini + * @param r + * @return + */ + public List<String> getRolesList(IniRealm r) { + List<String> roleList = new ArrayList<>(); + Map getIniRoles = r.getIni().get("roles"); + if (getIniRoles != null) { + Iterator it = getIniRoles.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + roleList.add(pair.getKey().toString().trim()); + } + } + return roleList; + } + /** * function to extract users from LDAP */ http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java index a079a44..7af52c8 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java @@ -18,9 +18,9 @@ package org.apache.zeppelin.rest; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.jdbc.JdbcRealm; -import org.apache.shiro.realm.ldap.AbstractLdapRealm; import org.apache.shiro.realm.ldap.JndiLdapRealm; import org.apache.shiro.realm.text.IniRealm; import org.apache.zeppelin.annotation.ZeppelinApi; @@ -98,6 +98,7 @@ public class SecurityRestApi { public Response getUserList(@PathParam("searchText") final String searchText) { List<String> usersList = new ArrayList<>(); + List<String> rolesList = new ArrayList<>(); try { GetUserList getUserListObj = new GetUserList(); Collection realmsList = SecurityUtils.getRealmsList(); @@ -107,6 +108,7 @@ public class SecurityRestApi { String name = realm.getName(); if (name.equals("iniRealm")) { usersList.addAll(getUserListObj.getUserList((IniRealm) realm)); + rolesList.addAll(getUserListObj.getRolesList((IniRealm) realm)); } else if (name.equals("ldapRealm")) { usersList.addAll(getUserListObj.getUserList((JndiLdapRealm) realm, searchText)); } else if (name.equals("activeDirectoryRealm")) { @@ -120,8 +122,10 @@ public class SecurityRestApi { } catch (Exception e) { LOG.error("Exception in retrieving Users from realms ", e); } - List<String> autoSuggestList = new ArrayList<>(); + List<String> autoSuggestUserList = new ArrayList<>(); + List<String> autoSuggestRoleList = new ArrayList<>(); Collections.sort(usersList); + Collections.sort(rolesList); Collections.sort(usersList, new Comparator<String>() { @Override public int compare(String o1, String o2) { @@ -134,18 +138,28 @@ public class SecurityRestApi { } }); int maxLength = 0; - for (int i = 0; i < usersList.size(); i++) { - String userLowerCase = usersList.get(i).toLowerCase(); - String searchTextLowerCase = searchText.toLowerCase(); - if (userLowerCase.indexOf(searchTextLowerCase) != -1) { + for (String user : usersList) { + if (StringUtils.containsIgnoreCase(user, searchText)) { + autoSuggestUserList.add(user); maxLength++; - autoSuggestList.add(usersList.get(i)); } if (maxLength == 5) { break; } } - return new JsonResponse<>(Response.Status.OK, "", autoSuggestList).build(); + + for (String role : rolesList) { + if (StringUtils.containsIgnoreCase(role, searchText)) { + autoSuggestRoleList.add(role); + } + } + + Map<String, List> returnListMap = new HashMap<>(); + returnListMap.put("users", autoSuggestUserList); + returnListMap.put("roles", autoSuggestRoleList); + + + return new JsonResponse<>(Response.Status.OK, "", returnListMap).build(); } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java index 671b213..ea810cb 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java @@ -161,12 +161,12 @@ public class AuthenticationIT extends AbstractZeppelinIT { pollingWait(By.xpath("//span[@tooltip='Note permissions']"), MAX_BROWSER_TIMEOUT_SEC).click(); - pollingWait(By.xpath("//input[@ng-model='permissions.owners']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); - pollingWait(By.xpath("//input[@ng-model='permissions.readers']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); - pollingWait(By.xpath("//input[@ng-model='permissions.writers']"), MAX_BROWSER_TIMEOUT_SEC) - .sendKeys("finance"); + pollingWait(By.xpath(".//*[@id='selectOwners']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); + pollingWait(By.xpath(".//*[@id='selectReaders']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); + pollingWait(By.xpath(".//*[@id='selectWriters']/following::span//input"), + MAX_BROWSER_TIMEOUT_SEC).sendKeys("finance "); pollingWait(By.xpath("//button[@ng-click='savePermissions()']"), MAX_BROWSER_TIMEOUT_SEC) .sendKeys(Keys.ENTER); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java index 54c31c1..b4ecd97 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/SecurityRestApiTest.java @@ -69,7 +69,7 @@ public class SecurityRestApiTest extends AbstractTestRestApi { get.addRequestHeader("Origin", "http://localhost"); Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>(){}.getType()); - List<String> userList = (List<String>) resp.get("body"); + List<String> userList = (List) ((Map) resp.get("body")).get("users"); collector.checkThat("Search result size", userList.size(), CoreMatchers.equalTo(1)); collector.checkThat("Search result contains admin", userList.contains("admin"), @@ -80,7 +80,7 @@ public class SecurityRestApiTest extends AbstractTestRestApi { notUser.addRequestHeader("Origin", "http://localhost"); Map<String, Object> notUserResp = gson.fromJson(notUser.getResponseBodyAsString(), new TypeToken<Map<String, Object>>(){}.getType()); - List<String> emptyUserList = (List<String>) notUserResp.get("body"); + List<String> emptyUserList = (List) ((Map) notUserResp.get("body")).get("users"); collector.checkThat("Search result size", emptyUserList.size(), CoreMatchers.equalTo(0)); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/bower.json ---------------------------------------------------------------------- diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 5d849b3..ae29f70 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -31,7 +31,8 @@ "ng-focus-if": "~1.0.2", "bootstrap3-dialog": "bootstrap-dialog#~1.34.7", "handsontable": "~0.24.2", - "moment-duration-format": "^1.3.0" + "moment-duration-format": "^1.3.0", + "select2": "^4.0.3" }, "devDependencies": { "angular-mocks": "1.5.0" http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/src/app/app.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 98d6b87..20ccfb1 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -98,6 +98,13 @@ var baseUrlSrv = angular.injector(['zeppelinWebApp']).get('baseUrlSrv'); // withCredentials when running locally via grunt $http.defaults.withCredentials = true; + jQuery.ajaxSetup({ + dataType: 'json', + xhrFields: { + withCredentials: true + }, + crossDomain: true + }); return $http.get(baseUrlSrv.getRestApiBase() + '/security/ticket').then(function(response) { zeppelinWebApp.run(function($rootScope) { $rootScope.ticket = angular.fromJson(response.data).body; http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/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 dc59f50..bf92fb7 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -43,16 +43,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro var connectedOnce = false; // user auto complete related - $scope.suggestions = []; - $scope.selectIndex = -1; - var selectedUser = ''; - var selectedUserIndex = 0; - var previousSelectedList = []; - var previousSelectedListOwners = []; - var previousSelectedListReaders = []; - var previousSelectedListWriters = []; - var searchText = []; - $scope.role = ''; $scope.noteRevisions = []; $scope.$on('setConnectedStatus', function(event, param) { @@ -556,6 +546,63 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro success(function(data, status, headers, config) { $scope.permissions = data.body; $scope.permissionsOrig = angular.copy($scope.permissions); // to check dirty + + var selectJson = { + tokenSeparators: [',', ' '], + ajax: { + url: function(params) { + if (!params.term) { + return false; + } + return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term; + }, + delay: 250, + processResults: function(data, params) { + var results = []; + + if (data.body.users.length !== 0) { + var users = []; + for (var len = 0; len < data.body.users.length; len++) { + users.push({ + 'id': data.body.users[len], + 'text': data.body.users[len] + }); + } + results.push({ + 'text': 'Users :', + 'children': users + }); + } + if (data.body.roles.length !== 0) { + var roles = []; + for (var len = 0; len < data.body.roles.length; len++) { + roles.push({ + 'id': data.body.roles[len], + 'text': data.body.roles[len] + }); + } + results.push({ + 'text': 'Roles :', + 'children': roles + }); + } + return { + results: results, + pagination: { + more: false + } + }; + }, + cache: false + }, + width: ' ', + tags: true, + minimumInputLength: 3 + }; + + angular.element('#selectOwners').select2(selectJson); + angular.element('#selectReaders').select2(selectJson); + angular.element('#selectWriters').select2(selectJson); if (callback) { callback(); } @@ -592,15 +639,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro }; function convertPermissionsToArray() { - if (!angular.isArray($scope.permissions.owners)) { - $scope.permissions.owners = $scope.permissions.owners.split(','); - } - if (!angular.isArray($scope.permissions.readers)) { - $scope.permissions.readers = $scope.permissions.readers.split(','); - } - if (!angular.isArray($scope.permissions.writers)) { - $scope.permissions.writers = $scope.permissions.writers.split(','); - } + $scope.permissions.owners = angular.element('#selectOwners').val(); + $scope.permissions.readers = angular.element('#selectReaders').val(); + $scope.permissions.writers = angular.element('#selectWriters').val(); } $scope.savePermissions = function() { @@ -652,6 +693,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro $scope.togglePermissions = function() { if ($scope.showPermissions) { $scope.closePermissions(); + angular.element('#selectOwners').select2({}); + angular.element('#selectReaders').select2({}); + angular.element('#selectWriters').select2({}); } else { $scope.openPermissions(); $scope.closeSetting(); @@ -674,195 +718,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $ro } }; - function checkPreviousRole(role) { - var i = 0; - if (role !== $scope.role) { - if ($scope.role === 'owners') { - previousSelectedListOwners = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListOwners[i] = previousSelectedList[i]; - } - } - if ($scope.role === 'readers') { - previousSelectedListReaders = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListReaders[i] = previousSelectedList[i]; - } - } - if ($scope.role === 'writers') { - previousSelectedListWriters = []; - for (i = 0; i < previousSelectedList.length; i++) { - previousSelectedListWriters[i] = previousSelectedList[i]; - } - } - - $scope.role = role; - previousSelectedList = []; - if (role === 'owners') { - for (i = 0; i < previousSelectedListOwners.length; i++) { - previousSelectedList[i] = previousSelectedListOwners[i]; - } - } - if (role === 'readers') { - for (i = 0; i < previousSelectedListReaders.length; i++) { - previousSelectedList[i] = previousSelectedListReaders[i]; - } - } - if (role === 'writers') { - for (i = 0; i < previousSelectedListWriters.length; i++) { - previousSelectedList[i] = previousSelectedListWriters[i]; - } - } - } - } - - function convertToArray(role) { - if (!$scope.permissions) { - return; - } else if (role === 'owners' && typeof $scope.permissions.owners === 'string') { - searchText = $scope.permissions.owners.split(','); - } else if (role === 'readers' && typeof $scope.permissions.readers === 'string') { - searchText = $scope.permissions.readers.split(','); - } else if (role === 'writers' && typeof $scope.permissions.writers === 'string') { - searchText = $scope.permissions.writers.split(','); - } - - for (var i = 0; i < searchText.length; i++) { - searchText[i] = searchText[i].trim(); - } - } - - function convertToString(role) { - if (role === 'owners') { - $scope.permissions.owners = searchText.join(); - } else if (role === 'readers') { - $scope.permissions.readers = searchText.join(); - } else if (role === 'writers') { - $scope.permissions.writers = searchText.join(); - } - } - - function getSuggestions(searchQuery) { - $scope.suggestions = []; - $http.get(baseUrlSrv.getRestApiBase() + '/security/userlist/' + searchQuery).then(function - (response) { - var userlist = angular.fromJson(response.data).body; - for (var k in userlist) { - $scope.suggestions.push(userlist[k]); - } - }); - } - - function updatePreviousList() { - for (var i = 0; i < searchText.length; i++) { - previousSelectedList[i] = searchText[i]; - } - } - - var getChangedIndex = function() { - if (previousSelectedList.length === 0) { - selectedUserIndex = searchText.length - 1; - } else { - for (var i = 0; i < searchText.length; i++) { - if (previousSelectedList[i] !== searchText[i]) { - selectedUserIndex = i; - previousSelectedList = []; - break; - } - } - } - updatePreviousList(); - }; - - $scope.$watch('permissions.owners', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('owners'); - }); - }, 350)); - - $scope.$watch('permissions.readers', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('readers'); - }); - }, 350)); - - $scope.$watch('permissions.writers', _.debounce(function(readers) { - $scope.$apply(function() { - $scope.search('writers'); - }); - }, 350)); - - // function to find suggestion list on change - $scope.search = function(role) { - angular.element('.userlist').show(); - convertToArray(role); - checkPreviousRole(role); - getChangedIndex(); - $scope.selectIndex = -1; - $scope.suggestions = []; - selectedUser = searchText[selectedUserIndex]; - if (selectedUser !== '') { - getSuggestions(selectedUser); - } else { - $scope.suggestions = []; - } - }; - - var checkIfSelected = function() { - if (($scope.suggestions.length === 0) && - ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length) || - ($scope.suggestions.length !== 0 && ($scope.selectIndex < 0 || $scope.selectIndex >= $scope.suggestions.length)) - ) { - searchText[selectedUserIndex] = selectedUser; - $scope.suggestions = []; - return true; - } else { - return false; - } - }; - - $scope.checkKeyDown = function(event, role) { - if (event.keyCode === 40) { - event.preventDefault(); - if ($scope.selectIndex + 1 !== $scope.suggestions.length) { - $scope.selectIndex++; - } - } else if (event.keyCode === 38) { - event.preventDefault(); - - if ($scope.selectIndex - 1 !== -1) { - $scope.selectIndex--; - - } - } else if (event.keyCode === 13) { - event.preventDefault(); - if (!checkIfSelected()) { - selectedUser = $scope.suggestions[$scope.selectIndex]; - searchText[selectedUserIndex] = $scope.suggestions[$scope.selectIndex]; - updatePreviousList(); - convertToString(role); - $scope.suggestions = []; - } - } - }; - - $scope.checkKeyUp = function(event) { - if (event.keyCode !== 8 || event.keyCode !== 46) { - if (searchText[selectedUserIndex] === '') { - $scope.suggestions = []; - } - } - }; - - $scope.assignValueAndHide = function(index, role) { - searchText[selectedUserIndex] = $scope.suggestions[index]; - updatePreviousList(); - convertToString(role); - $scope.suggestions = []; - }; - angular.element(document).click(function() { - angular.element('.userlist').hide(); angular.element('.ace_autocomplete').hide(); }); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/src/app/notebook/notebook.css ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css index ed45c67..c11544f 100644 --- a/zeppelin-web/src/app/notebook/notebook.css +++ b/zeppelin-web/src/app/notebook/notebook.css @@ -308,51 +308,7 @@ cursor: default; } -.userlist { - width: 230px; - font-family: Georgia, Times, serif; - font-size: 15px; - position: absolute; - z-index: 9999; -} - -.userlist ul { - list-style: none; -} - -.userlist ul li { - box-shadow: 3px 3px 5px #888888; - display: list-item; - text-decoration: none; - color: #000000; - background-color: #FFFFFF; - line-height: 30px; - border-bottom-style: none; - border-bottom-width: 1px; - border-bottom:1px #CCCCCC solid; - padding-left: 10px; - cursor: pointer; -} - -.userlist ul li:first-child { - border-top-right-radius: 5px; - border-top-left-radius: 5px; -} - -.userlist ul li:last-child { - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; -} - -.userlist ul li strong { - margin-right: 10px; -} - -.userlist li:hover { - background-color: #E0E0E0; -} - -.userlist li:active, -.userlist li.active { - background-color: #428BCA; +.select2-container--default{ + min-width: 150px; + max-width: 50%; } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/src/app/notebook/notebook.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html index fd329ac..9ad7166 100644 --- a/zeppelin-web/src/app/notebook/notebook.html +++ b/zeppelin-web/src/app/notebook/notebook.html @@ -70,57 +70,24 @@ limitations under the License. </p> <div class="permissionsForm" data-ng-model="permissions"> - <p><span class="owners">Owners </span> - <input ng-model="permissions.owners" - placeholder="search for users" - class="input" - ng-keydown="checkKeyDown($event,'owners')" - ng-keyup="checkKeyUp($event)"/> - Owners can change permissions,read and write the note. + <p><span class="owners">Owners </span> + <select id="selectOwners" multiple="multiple"> + <option ng-repeat="owner in permissions.owners" selected="selected">{{owner}}</option> + </select> + Owners can change permissions,read and write the note. </p> - <div ng-if="role === 'owners'" class="userlist" > - <ul> - <li ng-repeat="suggestion in suggestions" - ng-class="{active : selectIndex === $index }" - ng-click="assignValueAndHide($index,'owners')"> - {{suggestion}} - </li> - </ul> - </div> <p><span class="readers">Readers </span> - <input ng-model="permissions.readers" - placeholder="search for users" - class="input" - ng-keydown="checkKeyDown($event,'readers')" - ng-keyup="checkKeyUp($event)"/> + <select id="selectReaders" multiple="multiple"> + <option ng-repeat="readers in permissions.readers" selected="selected">{{readers}}</option> + </select> Readers can only read the note. </p> - <div ng-if="role === 'readers'" class="userlist"> - <ul> - <li ng-repeat="suggestion in suggestions" - ng-class="{active : selectIndex === $index }" - ng-click="assignValueAndHide($index,'readers')"> - {{suggestion}} - </li> - </ul> - </div> <p><span class="writers">Writers </span> - <input ng-model="permissions.writers" - placeholder="search for users" - class="input" - ng-keydown="checkKeyDown($event,'writers')" - ng-keyup="checkKeyUp($event)"/> + <select id="selectWriters" multiple="multiple"> + <option ng-repeat="writers in permissions.writers" selected="selected">{{writers}}</option> + </select> Writers can read and write the note. </p> - <div ng-if="role === 'writers'" class="userlist"> - <ul> - <li ng-repeat="suggestion in suggestions" - ng-class="{active : selectIndex === $index }" - ng-click="assignValueAndHide($index,'writers')"> - {{suggestion}} - </li> - </ul> - </div> </div> </div> <br /> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/src/index.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 9fe9489..ff1fa91 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -48,6 +48,7 @@ limitations under the License. <link rel="stylesheet" href="bower_components/handsontable/dist/handsontable.css" /> <!-- endbower --> <link rel="stylesheet" href="bower_components/jquery-ui/themes/base/jquery-ui.css" /> + <link rel="stylesheet" href="bower_components/select2/dist/css/select2.css" /> <!-- endbuild --> <!-- build:css(.tmp) styles/main.css --> <link rel="stylesheet" href="app/home/home.css" /> @@ -144,6 +145,7 @@ limitations under the License. <script src="bower_components/pikaday/pikaday.js"></script> <script src="bower_components/handsontable/dist/handsontable.js"></script> <script src="bower_components/moment-duration-format/lib/moment-duration-format.js"></script> + <script src="bower_components/select2/dist/js/select2.js"></script> <!-- endbower --> <!-- endbuild --> <!-- build:js({.tmp,src}) scripts/scripts.js --> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/1d0028bf/zeppelin-web/test/karma.conf.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js index 778f0f1..f9f03a4 100644 --- a/zeppelin-web/test/karma.conf.js +++ b/zeppelin-web/test/karma.conf.js @@ -64,6 +64,7 @@ module.exports = function(config) { 'bower_components/pikaday/pikaday.js', 'bower_components/handsontable/dist/handsontable.js', 'bower_components/moment-duration-format/lib/moment-duration-format.js', + 'bower_components/select2/dist/js/select2.js', 'bower_components/angular-mocks/angular-mocks.js', // endbower 'src/app/app.js',
