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)
![screen shot 2016-07-27 at 7 13 44 
pm](https://cloud.githubusercontent.com/assets/674497/17177288/4f5ac594-542e-11e6-8543-d62c238e8105.png)

### 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',

Reply via email to