Commit 0e570857957ddb3de09172751101754f95a4cb43

Authored by Dmitriy Zaporozhets
2 parents d1a5e370 ef05423f

Merge branch 'feature/select2-user-ajax'

Gemfile
... ... @@ -113,6 +113,7 @@ group :assets do
113 113 gem "therubyracer"
114 114  
115 115 gem 'chosen-rails', "0.9.8"
  116 + gem 'select2-rails'
116 117 gem 'jquery-atwho-rails', "0.1.7"
117 118 gem "jquery-rails", "2.1.3"
118 119 gem "jquery-ui-rails", "2.0.2"
... ...
Gemfile.lock
... ... @@ -384,6 +384,9 @@ GEM
384 384 seed-fu (2.2.0)
385 385 activerecord (~> 3.1)
386 386 activesupport (~> 3.1)
  387 + select2-rails (3.3.1)
  388 + sass-rails (>= 3.2)
  389 + thor (~> 0.14)
387 390 selenium-webdriver (2.30.0)
388 391 childprocess (>= 0.2.5)
389 392 multi_json (~> 1.0)
... ... @@ -534,6 +537,7 @@ DEPENDENCIES
534 537 sass-rails (~> 3.2.5)
535 538 sdoc
536 539 seed-fu
  540 + select2-rails
537 541 settingslogic
538 542 shoulda-matchers (= 1.3.0)
539 543 sidekiq
... ...
app/assets/javascripts/application.js
... ... @@ -17,6 +17,7 @@
17 17 //= require bootstrap
18 18 //= require modernizr
19 19 //= require chosen-jquery
  20 +//= require select2
20 21 //= require raphael
21 22 //= require g.raphael-min
22 23 //= require g.bar-min
... ...
app/assets/javascripts/md5.js 0 → 100644
... ... @@ -0,0 +1,211 @@
  1 +function md5 (str) {
  2 + // http://kevin.vanzonneveld.net
  3 + // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
  4 + // + namespaced by: Michael White (http://getsprink.com)
  5 + // + tweaked by: Jack
  6 + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  7 + // + input by: Brett Zamir (http://brett-zamir.me)
  8 + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  9 + // - depends on: utf8_encode
  10 + // * example 1: md5('Kevin van Zonneveld');
  11 + // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
  12 + var xl;
  13 +
  14 + var rotateLeft = function (lValue, iShiftBits) {
  15 + return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
  16 + };
  17 +
  18 + var addUnsigned = function (lX, lY) {
  19 + var lX4, lY4, lX8, lY8, lResult;
  20 + lX8 = (lX & 0x80000000);
  21 + lY8 = (lY & 0x80000000);
  22 + lX4 = (lX & 0x40000000);
  23 + lY4 = (lY & 0x40000000);
  24 + lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
  25 + if (lX4 & lY4) {
  26 + return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
  27 + }
  28 + if (lX4 | lY4) {
  29 + if (lResult & 0x40000000) {
  30 + return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
  31 + } else {
  32 + return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
  33 + }
  34 + } else {
  35 + return (lResult ^ lX8 ^ lY8);
  36 + }
  37 + };
  38 +
  39 + var _F = function (x, y, z) {
  40 + return (x & y) | ((~x) & z);
  41 + };
  42 + var _G = function (x, y, z) {
  43 + return (x & z) | (y & (~z));
  44 + };
  45 + var _H = function (x, y, z) {
  46 + return (x ^ y ^ z);
  47 + };
  48 + var _I = function (x, y, z) {
  49 + return (y ^ (x | (~z)));
  50 + };
  51 +
  52 + var _FF = function (a, b, c, d, x, s, ac) {
  53 + a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
  54 + return addUnsigned(rotateLeft(a, s), b);
  55 + };
  56 +
  57 + var _GG = function (a, b, c, d, x, s, ac) {
  58 + a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
  59 + return addUnsigned(rotateLeft(a, s), b);
  60 + };
  61 +
  62 + var _HH = function (a, b, c, d, x, s, ac) {
  63 + a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
  64 + return addUnsigned(rotateLeft(a, s), b);
  65 + };
  66 +
  67 + var _II = function (a, b, c, d, x, s, ac) {
  68 + a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
  69 + return addUnsigned(rotateLeft(a, s), b);
  70 + };
  71 +
  72 + var convertToWordArray = function (str) {
  73 + var lWordCount;
  74 + var lMessageLength = str.length;
  75 + var lNumberOfWords_temp1 = lMessageLength + 8;
  76 + var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
  77 + var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
  78 + var lWordArray = new Array(lNumberOfWords - 1);
  79 + var lBytePosition = 0;
  80 + var lByteCount = 0;
  81 + while (lByteCount < lMessageLength) {
  82 + lWordCount = (lByteCount - (lByteCount % 4)) / 4;
  83 + lBytePosition = (lByteCount % 4) * 8;
  84 + lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
  85 + lByteCount++;
  86 + }
  87 + lWordCount = (lByteCount - (lByteCount % 4)) / 4;
  88 + lBytePosition = (lByteCount % 4) * 8;
  89 + lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
  90 + lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
  91 + lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
  92 + return lWordArray;
  93 + };
  94 +
  95 + var wordToHex = function (lValue) {
  96 + var wordToHexValue = "",
  97 + wordToHexValue_temp = "",
  98 + lByte, lCount;
  99 + for (lCount = 0; lCount <= 3; lCount++) {
  100 + lByte = (lValue >>> (lCount * 8)) & 255;
  101 + wordToHexValue_temp = "0" + lByte.toString(16);
  102 + wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
  103 + }
  104 + return wordToHexValue;
  105 + };
  106 +
  107 + var x = [],
  108 + k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
  109 + S12 = 12,
  110 + S13 = 17,
  111 + S14 = 22,
  112 + S21 = 5,
  113 + S22 = 9,
  114 + S23 = 14,
  115 + S24 = 20,
  116 + S31 = 4,
  117 + S32 = 11,
  118 + S33 = 16,
  119 + S34 = 23,
  120 + S41 = 6,
  121 + S42 = 10,
  122 + S43 = 15,
  123 + S44 = 21;
  124 +
  125 + str = this.utf8_encode(str);
  126 + x = convertToWordArray(str);
  127 + a = 0x67452301;
  128 + b = 0xEFCDAB89;
  129 + c = 0x98BADCFE;
  130 + d = 0x10325476;
  131 +
  132 + xl = x.length;
  133 + for (k = 0; k < xl; k += 16) {
  134 + AA = a;
  135 + BB = b;
  136 + CC = c;
  137 + DD = d;
  138 + a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
  139 + d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
  140 + c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
  141 + b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
  142 + a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
  143 + d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
  144 + c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
  145 + b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
  146 + a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
  147 + d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
  148 + c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
  149 + b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
  150 + a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
  151 + d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
  152 + c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
  153 + b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
  154 + a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
  155 + d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
  156 + c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
  157 + b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
  158 + a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
  159 + d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
  160 + c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
  161 + b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
  162 + a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
  163 + d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
  164 + c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
  165 + b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
  166 + a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
  167 + d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
  168 + c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
  169 + b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
  170 + a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
  171 + d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
  172 + c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
  173 + b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
  174 + a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
  175 + d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
  176 + c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
  177 + b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
  178 + a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
  179 + d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
  180 + c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
  181 + b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
  182 + a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
  183 + d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
  184 + c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
  185 + b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
  186 + a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
  187 + d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
  188 + c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
  189 + b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
  190 + a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
  191 + d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
  192 + c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
  193 + b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
  194 + a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
  195 + d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
  196 + c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
  197 + b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
  198 + a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
  199 + d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
  200 + c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
  201 + b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
  202 + a = addUnsigned(a, AA);
  203 + b = addUnsigned(b, BB);
  204 + c = addUnsigned(c, CC);
  205 + d = addUnsigned(d, DD);
  206 + }
  207 +
  208 + var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
  209 +
  210 + return temp.toLowerCase();
  211 +}
... ...
app/assets/javascripts/users_select.js.coffee 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +$ ->
  2 + userFormatResult = (user) ->
  3 + avatar = gon.gravatar_url
  4 + avatar = avatar.replace('%{hash}', md5(user.email))
  5 + avatar = avatar.replace('%{size}', '24')
  6 +
  7 + markup = "<div class='user-result'>"
  8 + markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>"
  9 + markup += "<div class='user-name'>" + user.name + "</div>"
  10 + markup += "<div class='user-username'>" + user.username + "</div>"
  11 + markup += "</div>"
  12 + markup
  13 +
  14 + userFormatSelection = (user) ->
  15 + user.name
  16 +
  17 + $('.ajax-users-select').select2
  18 + placeholder: "Search for a user"
  19 + multiple: $('.ajax-users-select').hasClass('multiselect')
  20 + minimumInputLength: 0
  21 + ajax: # instead of writing the function to execute the request we use Select2's convenient helper
  22 + url: "/api/" + gon.api_version + "/users.json"
  23 + dataType: "json"
  24 + data: (term, page) ->
  25 + search: term # search term
  26 + per_page: 10
  27 + private_token: gon.api_token
  28 +
  29 + results: (data, page) -> # parse the results into the format expected by Select2.
  30 + # since we are using custom formatting functions we do not need to alter remote JSON data
  31 + results: data
  32 +
  33 + initSelection: (element, callback) ->
  34 + id = $(element).val()
  35 + if id isnt ""
  36 + $.ajax(
  37 + "/api/" + gon.api_version + "/users/" + id + ".json",
  38 + dataType: "json"
  39 + data:
  40 + private_token: gon.api_token
  41 + ).done (data) ->
  42 + callback data
  43 +
  44 +
  45 + formatResult: userFormatResult # omitted for brevity, see the source of this page
  46 + formatSelection: userFormatSelection # omitted for brevity, see the source of this page
  47 + dropdownCssClass: "ajax-users-dropdown" # apply css that makes the dropdown taller
  48 + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
  49 + m
  50 +
... ...
app/assets/javascripts/utf8_encode.js 0 → 100644
... ... @@ -0,0 +1,70 @@
  1 +function utf8_encode (argString) {
  2 + // http://kevin.vanzonneveld.net
  3 + // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
  4 + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  5 + // + improved by: sowberry
  6 + // + tweaked by: Jack
  7 + // + bugfixed by: Onno Marsman
  8 + // + improved by: Yves Sucaet
  9 + // + bugfixed by: Onno Marsman
  10 + // + bugfixed by: Ulrich
  11 + // + bugfixed by: Rafal Kukawski
  12 + // + improved by: kirilloid
  13 + // + bugfixed by: kirilloid
  14 + // * example 1: utf8_encode('Kevin van Zonneveld');
  15 + // * returns 1: 'Kevin van Zonneveld'
  16 +
  17 + if (argString === null || typeof argString === "undefined") {
  18 + return "";
  19 + }
  20 +
  21 + var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  22 + var utftext = '',
  23 + start, end, stringl = 0;
  24 +
  25 + start = end = 0;
  26 + stringl = string.length;
  27 + for (var n = 0; n < stringl; n++) {
  28 + var c1 = string.charCodeAt(n);
  29 + var enc = null;
  30 +
  31 + if (c1 < 128) {
  32 + end++;
  33 + } else if (c1 > 127 && c1 < 2048) {
  34 + enc = String.fromCharCode(
  35 + (c1 >> 6) | 192,
  36 + ( c1 & 63) | 128
  37 + );
  38 + } else if (c1 & 0xF800 != 0xD800) {
  39 + enc = String.fromCharCode(
  40 + (c1 >> 12) | 224,
  41 + ((c1 >> 6) & 63) | 128,
  42 + ( c1 & 63) | 128
  43 + );
  44 + } else { // surrogate pairs
  45 + if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); }
  46 + var c2 = string.charCodeAt(++n);
  47 + if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); }
  48 + c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
  49 + enc = String.fromCharCode(
  50 + (c1 >> 18) | 240,
  51 + ((c1 >> 12) & 63) | 128,
  52 + ((c1 >> 6) & 63) | 128,
  53 + ( c1 & 63) | 128
  54 + );
  55 + }
  56 + if (enc !== null) {
  57 + if (end > start) {
  58 + utftext += string.slice(start, end);
  59 + }
  60 + utftext += enc;
  61 + start = end = n + 1;
  62 + }
  63 + }
  64 +
  65 + if (end > start) {
  66 + utftext += string.slice(start, stringl);
  67 + }
  68 +
  69 + return utftext;
  70 +}
... ...
app/assets/stylesheets/application.scss
... ... @@ -5,6 +5,7 @@
5 5 *= require jquery.ui.gitlab
6 6 *= require jquery.atwho
7 7 *= require chosen
  8 + *= require select2
8 9 *= require_self
9 10 */
10 11  
... ... @@ -14,7 +15,7 @@
14 15 @import "gitlab_bootstrap.scss";
15 16  
16 17 @import "common.scss";
17   -@import "ref_select.scss";
  18 +@import "selects.scss";
18 19  
19 20 @import "sections/header.scss";
20 21 @import "sections/nav.scss";
... ...
app/assets/stylesheets/common.scss
... ... @@ -554,3 +554,4 @@ img.emoji {
554 554 .appear-data {
555 555 display: none;
556 556 }
  557 +
... ...
app/assets/stylesheets/ref_select.scss
... ... @@ -1,90 +0,0 @@
1   -/** Branch/tag selector **/
2   -.project-refs-form {
3   - margin: 0;
4   - span {
5   - background:none !important;
6   - position:static !important;
7   - width:auto !important;
8   - height:auto !important;
9   - }
10   -}
11   -.project-refs-select {
12   - width: 120px;
13   -}
14   -
15   -.project-refs-form .chzn-container {
16   - position: relative;
17   - top: 0;
18   - left: 0;
19   - margin-right: 10px;
20   -
21   - .chzn-drop {
22   - min-width: 400px;
23   - .chzn-results {
24   - max-height: 300px;
25   - }
26   - .chzn-search input {
27   - min-width: 365px;
28   - }
29   - }
30   -}
31   -
32   -/** Fix for Search Dropdown Border **/
33   -.chzn-container {
34   - .chzn-search {
35   - input:focus {
36   - @include box-shadow(none);
37   - }
38   - }
39   -
40   - .chzn-drop {
41   - margin: 7px 0;
42   - min-width: 200px;
43   - border: 1px solid #bbb;
44   - @include border-radius(0);
45   -
46   - .chzn-results {
47   - margin-top: 5px;
48   - max-height: 300px;
49   -
50   - .group-result {
51   - color: $style_color;
52   - border-bottom: 1px solid #EEE;
53   - padding: 8px;
54   - }
55   - .active-result {
56   - @include border-radius(0);
57   -
58   - &.highlighted {
59   - background: $hover;
60   - color: $style_color;
61   - }
62   - &.result-selected {
63   - background: #EEE;
64   - border-left: 4px solid #CCC;
65   - }
66   - }
67   - }
68   -
69   - .chzn-search {
70   - @include bg-gray-gradient;
71   - input {
72   - min-width: 165px;
73   - border-color: #CCC;
74   - }
75   - }
76   - }
77   -
78   - .chzn-single {
79   - @include bg-light-gray-gradient;
80   -
81   - div {
82   - background: transparent;
83   - border-left: none;
84   - }
85   -
86   - span {
87   - font-weight: normal;
88   - }
89   - }
90   -}
app/assets/stylesheets/selects.scss 0 → 100644
... ... @@ -0,0 +1,110 @@
  1 +.ajax-users-select {
  2 + width: 400px;
  3 +}
  4 +
  5 +.user-result {
  6 + .user-image {
  7 + float: left;
  8 + }
  9 + .user-name {
  10 + }
  11 + .user-username {
  12 + color: #999;
  13 + }
  14 +}
  15 +
  16 +.select2-no-results {
  17 + padding: 7px;
  18 + color: #666;
  19 +}
  20 +
  21 +/** Branch/tag selector **/
  22 +.project-refs-form {
  23 + margin: 0;
  24 + span {
  25 + background:none !important;
  26 + position:static !important;
  27 + width:auto !important;
  28 + height:auto !important;
  29 + }
  30 +}
  31 +.project-refs-select {
  32 + width: 120px;
  33 +}
  34 +
  35 +.project-refs-form .chzn-container {
  36 + position: relative;
  37 + top: 0;
  38 + left: 0;
  39 + margin-right: 10px;
  40 +
  41 + .chzn-drop {
  42 + min-width: 400px;
  43 + .chzn-results {
  44 + max-height: 300px;
  45 + }
  46 + .chzn-search input {
  47 + min-width: 365px;
  48 + }
  49 + }
  50 +}
  51 +
  52 +/** Fix for Search Dropdown Border **/
  53 +.chzn-container {
  54 + .chzn-search {
  55 + input:focus {
  56 + @include box-shadow(none);
  57 + }
  58 + }
  59 +
  60 + .chzn-drop {
  61 + margin: 7px 0;
  62 + min-width: 200px;
  63 + border: 1px solid #bbb;
  64 + @include border-radius(0);
  65 +
  66 + .chzn-results {
  67 + margin-top: 5px;
  68 + max-height: 300px;
  69 +
  70 + .group-result {
  71 + color: $style_color;
  72 + border-bottom: 1px solid #EEE;
  73 + padding: 8px;
  74 + }
  75 + .active-result {
  76 + @include border-radius(0);
  77 +
  78 + &.highlighted {
  79 + background: $hover;
  80 + color: $style_color;
  81 + }
  82 + &.result-selected {
  83 + background: #EEE;
  84 + border-left: 4px solid #CCC;
  85 + }
  86 + }
  87 + }
  88 +
  89 + .chzn-search {
  90 + @include bg-gray-gradient;
  91 + input {
  92 + min-width: 165px;
  93 + border-color: #CCC;
  94 + }
  95 + }
  96 + }
  97 +
  98 + .chzn-single {
  99 + @include bg-light-gray-gradient;
  100 +
  101 + div {
  102 + background: transparent;
  103 + border-left: none;
  104 + }
  105 +
  106 + span {
  107 + font-weight: normal;
  108 + }
  109 + }
  110 +}
... ...
app/controllers/application_controller.rb
... ... @@ -152,5 +152,8 @@ class ApplicationController &lt; ActionController::Base
152 152  
153 153 def add_gon_variables
154 154 gon.default_issues_tracker = Project.issues_tracker.default_value
  155 + gon.api_version = Gitlab::API.version
  156 + gon.api_token = current_user.private_token if current_user
  157 + gon.gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
155 158 end
156 159 end
... ...
app/controllers/team_members_controller.rb
... ... @@ -16,7 +16,7 @@ class TeamMembersController &lt; ProjectResourceController
16 16 end
17 17  
18 18 def create
19   - users = User.where(id: params[:user_ids])
  19 + users = User.where(id: params[:user_ids].split(','))
20 20  
21 21 @project.team << [users, params[:project_access]]
22 22  
... ...
app/controllers/teams/members_controller.rb
... ... @@ -13,7 +13,7 @@ class Teams::MembersController &lt; Teams::ApplicationController
13 13  
14 14 def create
15 15 unless params[:user_ids].blank?
16   - user_ids = params[:user_ids]
  16 + user_ids = params[:user_ids].split(',')
17 17 access = params[:default_project_access]
18 18 is_admin = params[:group_admin]
19 19 user_team.add_members(user_ids, access, is_admin)
... ...
app/helpers/application_helper.rb
... ... @@ -169,4 +169,10 @@ module ApplicationHelper
169 169 end
170 170  
171 171 alias_method :url_to_image, :image_url
  172 +
  173 + def users_select_tag(id, opts = {})
  174 + css_class = "ajax-users-select"
  175 + css_class << " multiselect" if opts[:multiple]
  176 + hidden_field_tag(id, '', class: css_class)
  177 + end
172 178 end
... ...
app/models/user_team.rb
... ... @@ -69,6 +69,9 @@ class UserTeam &lt; ActiveRecord::Base
69 69 end
70 70  
71 71 def add_members(users, access, group_admin)
  72 + # reject existing users
  73 + users.reject! { |id| member_ids.include?(id.to_i) }
  74 +
72 75 users.each do |user|
73 76 add_member(user, access, group_admin)
74 77 end
... ...
app/views/team_members/_form.html.haml
... ... @@ -11,7 +11,8 @@
11 11 %h6 1. Choose people you want in the team
12 12 .clearfix
13 13 = f.label :user_ids, "People"
14   - .input= select_tag(:user_ids, options_from_collection_for_select(User.active.not_in_project(@project).alphabetically, :id, :name_with_username), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
  14 + .input
  15 + = users_select_tag(:user_ids, multiple: true)
15 16  
16 17 %h6 2. Set access level for them
17 18 .clearfix
... ...
app/views/teams/members/new.html.haml
... ... @@ -20,7 +20,8 @@
20 20 %td= @team.admin?(member) ? "Admin" : "Member"
21 21 %td
22 22 %tr
23   - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_username), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
  23 + %td
  24 + = users_select_tag(:user_ids, multiple: true)
24 25 %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
25 26 %td
26 27 %span= check_box_tag :group_admin
... ...
features/project/team_management.feature
... ... @@ -11,6 +11,7 @@ Feature: Project Team management
11 11 Then I should be able to see myself in team
12 12 And I should see "Sam" in team list
13 13  
  14 + @javascript
14 15 Scenario: Add user to project
15 16 Given I click link "New Team Member"
16 17 And I select "Mike" as "Reporter"
... ...
features/steps/project/project_team_management.rb
... ... @@ -2,6 +2,7 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps
2 2 include SharedAuthentication
3 3 include SharedProject
4 4 include SharedPaths
  5 + include Select2Helper
5 6  
6 7 Then 'I should be able to see myself in team' do
7 8 page.should have_content(@user.name)
... ... @@ -20,8 +21,9 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps
20 21  
21 22 And 'I select "Mike" as "Reporter"' do
22 23 user = User.find_by_name("Mike")
  24 +
  25 + select2(user.id, from: "#user_ids", multiple: true)
23 26 within "#new_team_member" do
24   - select "#{user.name} (#{user.username})", :from => "user_ids"
25 27 select "Reporter", :from => "project_access"
26 28 end
27 29 click_button "Add users"
... ...
features/steps/userteams/userteams.rb
... ... @@ -2,238 +2,239 @@ class Userteams &lt; Spinach::FeatureSteps
2 2 include SharedAuthentication
3 3 include SharedPaths
4 4 include SharedProject
  5 + include Select2Helper
5 6  
6   - When 'I do not have teams with me' do
7   - UserTeam.with_member(current_user).destroy_all
8   - end
  7 + When 'I do not have teams with me' do
  8 + UserTeam.with_member(current_user).destroy_all
  9 + end
9 10  
10   - Then 'I should see dashboard page without teams info block' do
11   - page.has_no_css?(".teams-box").must_equal true
12   - end
  11 + Then 'I should see dashboard page without teams info block' do
  12 + page.has_no_css?(".teams-box").must_equal true
  13 + end
13 14  
14   - When 'I have teams with my membership' do
15   - team = create :user_team, owner: current_user
16   - team.add_member(current_user, UserTeam.access_roles["Master"], true)
17   - end
  15 + When 'I have teams with my membership' do
  16 + team = create :user_team, owner: current_user
  17 + team.add_member(current_user, UserTeam.access_roles["Master"], true)
  18 + end
18 19  
19   - Then 'I should see dashboard page with teams information block' do
20   - page.should have_css(".teams-box")
21   - end
  20 + Then 'I should see dashboard page with teams information block' do
  21 + page.should have_css(".teams-box")
  22 + end
22 23  
23   - When 'exist user teams' do
24   - team = create :user_team
25   - team.add_member(current_user, UserTeam.access_roles["Master"], true)
26   - end
  24 + When 'exist user teams' do
  25 + team = create :user_team
  26 + team.add_member(current_user, UserTeam.access_roles["Master"], true)
  27 + end
27 28  
28   - And 'I click on "All teams" link' do
29   - click_link("All Teams")
30   - end
  29 + And 'I click on "All teams" link' do
  30 + click_link("All Teams")
  31 + end
31 32  
32   - Then 'I should see "All teams" page' do
33   - current_path.should == teams_path
34   - end
  33 + Then 'I should see "All teams" page' do
  34 + current_path.should == teams_path
  35 + end
35 36  
36   - And 'I should see exist teams in teams list' do
37   - team = UserTeam.last
38   - find_in_list(".teams_list tr", team).must_equal true
39   - end
  37 + And 'I should see exist teams in teams list' do
  38 + team = UserTeam.last
  39 + find_in_list(".teams_list tr", team).must_equal true
  40 + end
40 41  
41   - When 'I click to "New team" link' do
42   - click_link("New Team")
43   - end
  42 + When 'I click to "New team" link' do
  43 + click_link("New Team")
  44 + end
44 45  
45   - And 'I submit form with new team info' do
46   - fill_in 'name', with: 'gitlab'
  46 + And 'I submit form with new team info' do
  47 + fill_in 'name', with: 'gitlab'
47 48  
48   - fill_in 'user_team_description', with: 'team description'
49   - click_button 'Create team'
50   - end
  49 + fill_in 'user_team_description', with: 'team description'
  50 + click_button 'Create team'
  51 + end
51 52  
52   - And 'I should see newly created team' do
53   - page.should have_content "gitlab"
54   - page.should have_content "team description"
55   - end
  53 + And 'I should see newly created team' do
  54 + page.should have_content "gitlab"
  55 + page.should have_content "team description"
  56 + end
56 57  
57   - Then 'I should be redirected to new team page' do
58   - team = UserTeam.last
59   - current_path.should == team_path(team)
60   - end
  58 + Then 'I should be redirected to new team page' do
  59 + team = UserTeam.last
  60 + current_path.should == team_path(team)
  61 + end
61 62  
62   - When 'I have teams with projects and members' do
63   - team = create :user_team, owner: current_user
64   - @project = create :project
65   - team.add_member(current_user, UserTeam.access_roles["Master"], true)
66   - team.assign_to_project(@project, UserTeam.access_roles["Master"])
67   - @event = create(:closed_issue_event, project: @project)
68   - end
  63 + When 'I have teams with projects and members' do
  64 + team = create :user_team, owner: current_user
  65 + @project = create :project
  66 + team.add_member(current_user, UserTeam.access_roles["Master"], true)
  67 + team.assign_to_project(@project, UserTeam.access_roles["Master"])
  68 + @event = create(:closed_issue_event, project: @project)
  69 + end
69 70  
70   - When 'I visit team page' do
71   - visit team_path(UserTeam.last)
72   - end
  71 + When 'I visit team page' do
  72 + visit team_path(UserTeam.last)
  73 + end
73 74  
74   - Then 'I should see projects list' do
75   - page.should have_css(".projects_box")
76   - projects_box = find(".projects_box")
77   - projects_box.should have_content(@project.name)
78   - end
  75 + Then 'I should see projects list' do
  76 + page.should have_css(".projects_box")
  77 + projects_box = find(".projects_box")
  78 + projects_box.should have_content(@project.name)
  79 + end
79 80  
80   - And 'project from team has issues assigned to me' do
81   - team = UserTeam.last
82   - team.projects.each do |project|
83   - project.issues << create(:issue, assignee: current_user)
84   - end
  81 + And 'project from team has issues assigned to me' do
  82 + team = UserTeam.last
  83 + team.projects.each do |project|
  84 + project.issues << create(:issue, assignee: current_user)
85 85 end
  86 + end
86 87  
87   - When 'I visit team issues page' do
88   - team = UserTeam.last
89   - visit issues_team_path(team)
90   - end
  88 + When 'I visit team issues page' do
  89 + team = UserTeam.last
  90 + visit issues_team_path(team)
  91 + end
91 92  
92   - Then 'I should see issues from this team assigned to me' do
93   - team = UserTeam.last
94   - team.projects.each do |project|
95   - project.issues.assigned(current_user).each do |issue|
96   - page.should have_content issue.title
97   - end
  93 + Then 'I should see issues from this team assigned to me' do
  94 + team = UserTeam.last
  95 + team.projects.each do |project|
  96 + project.issues.assigned(current_user).each do |issue|
  97 + page.should have_content issue.title
98 98 end
99 99 end
  100 + end
100 101  
101   - Given 'I have team with projects and members' do
102   - team = create :user_team, owner: current_user
103   - project = create :project
104   - user = create :user
105   - team.add_member(current_user, UserTeam.access_roles["Master"], true)
106   - team.add_member(user, UserTeam.access_roles["Developer"], false)
107   - team.assign_to_project(project, UserTeam.access_roles["Master"])
108   - end
  102 + Given 'I have team with projects and members' do
  103 + team = create :user_team, owner: current_user
  104 + project = create :project
  105 + user = create :user
  106 + team.add_member(current_user, UserTeam.access_roles["Master"], true)
  107 + team.add_member(user, UserTeam.access_roles["Developer"], false)
  108 + team.assign_to_project(project, UserTeam.access_roles["Master"])
  109 + end
109 110  
110   - Given 'project from team has issues assigned to teams members' do
111   - team = UserTeam.last
112   - team.projects.each do |project|
113   - team.members.each do |member|
114   - project.issues << create(:issue, assignee: member)
115   - end
  111 + Given 'project from team has issues assigned to teams members' do
  112 + team = UserTeam.last
  113 + team.projects.each do |project|
  114 + team.members.each do |member|
  115 + project.issues << create(:issue, assignee: member)
116 116 end
117 117 end
  118 + end
118 119  
119   - Then 'I should see issues from this team assigned to teams members' do
120   - team = UserTeam.last
121   - team.projects.each do |project|
122   - team.members.each do |member|
123   - project.issues.assigned(member).each do |issue|
124   - page.should have_content issue.title
125   - end
  120 + Then 'I should see issues from this team assigned to teams members' do
  121 + team = UserTeam.last
  122 + team.projects.each do |project|
  123 + team.members.each do |member|
  124 + project.issues.assigned(member).each do |issue|
  125 + page.should have_content issue.title
126 126 end
127 127 end
128 128 end
  129 + end
129 130  
130   - Given 'project from team has merge requests assigned to me' do
131   - team = UserTeam.last
132   - team.projects.each do |project|
133   - team.members.each do |member|
134   - 3.times { project.merge_requests << create(:merge_request, assignee: member) }
135   - end
  131 + Given 'project from team has merge requests assigned to me' do
  132 + team = UserTeam.last
  133 + team.projects.each do |project|
  134 + team.members.each do |member|
  135 + 3.times { project.merge_requests << create(:merge_request, assignee: member) }
136 136 end
137 137 end
  138 + end
138 139  
139   - When 'I visit team merge requests page' do
140   - team = UserTeam.last
141   - visit merge_requests_team_path(team)
142   - end
  140 + When 'I visit team merge requests page' do
  141 + team = UserTeam.last
  142 + visit merge_requests_team_path(team)
  143 + end
143 144  
144   - Then 'I should see merge requests from this team assigned to me' do
145   - team = UserTeam.last
146   - team.projects.each do |project|
147   - team.members.each do |member|
148   - project.issues.assigned(member).each do |merge_request|
149   - page.should have_content merge_request.title
150   - end
  145 + Then 'I should see merge requests from this team assigned to me' do
  146 + team = UserTeam.last
  147 + team.projects.each do |project|
  148 + team.members.each do |member|
  149 + project.issues.assigned(member).each do |merge_request|
  150 + page.should have_content merge_request.title
151 151 end
152 152 end
153 153 end
  154 + end
154 155  
155   - Given 'project from team has merge requests assigned to team members' do
156   - team = UserTeam.last
157   - team.projects.each do |project|
158   - team.members.each do |member|
159   - 3.times { project.merge_requests << create(:merge_request, assignee: member) }
160   - end
  156 + Given 'project from team has merge requests assigned to team members' do
  157 + team = UserTeam.last
  158 + team.projects.each do |project|
  159 + team.members.each do |member|
  160 + 3.times { project.merge_requests << create(:merge_request, assignee: member) }
161 161 end
162 162 end
  163 + end
163 164  
164   - Then 'I should see merge requests from this team assigned to me' do
165   - team = UserTeam.last
166   - team.projects.each do |project|
167   - team.members.each do |member|
168   - project.issues.assigned(member).each do |merge_request|
169   - page.should have_content merge_request.title
170   - end
  165 + Then 'I should see merge requests from this team assigned to me' do
  166 + team = UserTeam.last
  167 + team.projects.each do |project|
  168 + team.members.each do |member|
  169 + project.issues.assigned(member).each do |merge_request|
  170 + page.should have_content merge_request.title
171 171 end
172 172 end
173 173 end
  174 + end
174 175  
175   - Given 'I have new user "John"' do
176   - create :user, name: "John"
177   - end
  176 + Given 'I have new user "John"' do
  177 + create :user, name: "John"
  178 + end
178 179  
179   - When 'I visit team people page' do
180   - team = UserTeam.last
181   - visit team_members_path(team)
182   - end
  180 + When 'I visit team people page' do
  181 + team = UserTeam.last
  182 + visit team_members_path(team)
  183 + end
183 184  
184   - And 'I select user "John" from list with role "Reporter"' do
185   - user = User.find_by_name("John")
186   - within "#team_members" do
187   - select "#{user.name} (#{user.username})", from: "user_ids"
188   - select "Reporter", from: "default_project_access"
189   - end
190   - click_button "Add"
  185 + And 'I select user "John" from list with role "Reporter"' do
  186 + user = User.find_by_name("John")
  187 + select2(user.id, from: "#user_ids", multiple: true)
  188 + within "#team_members" do
  189 + select "Reporter", from: "default_project_access"
191 190 end
  191 + click_button "Add"
  192 + end
192 193  
193   - Then 'I should see user "John" in team list' do
194   - user = User.find_by_name("John")
195   - team_members_list = find(".team-table")
196   - team_members_list.should have_content user.name
197   - end
  194 + Then 'I should see user "John" in team list' do
  195 + user = User.find_by_name("John")
  196 + team_members_list = find(".team-table")
  197 + team_members_list.should have_content user.name
  198 + end
198 199  
199   - And 'I have my own project without teams' do
200   - @project = create :project, namespace: current_user.namespace
201   - end
  200 + And 'I have my own project without teams' do
  201 + @project = create :project, namespace: current_user.namespace
  202 + end
202 203  
203   - And 'I visit my team page' do
204   - team = UserTeam.where(owner_id: current_user.id).last
205   - visit team_path(team)
206   - end
  204 + And 'I visit my team page' do
  205 + team = UserTeam.where(owner_id: current_user.id).last
  206 + visit team_path(team)
  207 + end
207 208  
208   - When 'I click on link "Projects"' do
209   - click_link "Projects"
210   - end
  209 + When 'I click on link "Projects"' do
  210 + click_link "Projects"
  211 + end
211 212  
212   - And 'I click link "Assign project to Team"' do
213   - click_link "Assign project to Team"
214   - end
  213 + And 'I click link "Assign project to Team"' do
  214 + click_link "Assign project to Team"
  215 + end
215 216  
216   - Then 'I should see form with my own project in avaliable projects list' do
217   - projects_select = find("#project_ids")
218   - projects_select.should have_content(@project.name)
219   - end
  217 + Then 'I should see form with my own project in avaliable projects list' do
  218 + projects_select = find("#project_ids")
  219 + projects_select.should have_content(@project.name)
  220 + end
220 221  
221   - When 'I submit form with selected project and max access' do
222   - within "#assign_projects" do
223   - select @project.name_with_namespace, from: "project_ids"
224   - select "Reporter", from: "greatest_project_access"
225   - end
226   - click_button "Add"
  222 + When 'I submit form with selected project and max access' do
  223 + within "#assign_projects" do
  224 + select @project.name_with_namespace, from: "project_ids"
  225 + select "Reporter", from: "greatest_project_access"
227 226 end
  227 + click_button "Add"
  228 + end
228 229  
229   - Then 'I should see my own project in team projects list' do
230   - projects = find(".projects-table")
231   - projects.should have_content(@project.name)
232   - end
  230 + Then 'I should see my own project in team projects list' do
  231 + projects = find(".projects-table")
  232 + projects.should have_content(@project.name)
  233 + end
233 234  
234   - When 'I click link "New Team Member"' do
235   - click_link "New Team Member"
236   - end
  235 + When 'I click link "New Team Member"' do
  236 + click_link "New Team Member"
  237 + end
237 238  
238 239 protected
239 240  
... ... @@ -257,5 +258,4 @@ class Userteams &lt; Spinach::FeatureSteps
257 258 end
258 259 entered
259 260 end
260   -
261 261 end
... ...
features/support/env.rb
... ... @@ -14,7 +14,7 @@ require &#39;spinach/capybara&#39;
14 14 require 'sidekiq/testing/inline'
15 15  
16 16  
17   -%w(stubbed_repository valid_commit).each do |f|
  17 +%w(stubbed_repository valid_commit select2_helper).each do |f|
18 18 require Rails.root.join('spec', 'support', f)
19 19 end
20 20  
... ...
features/teams/team.feature
... ... @@ -46,6 +46,7 @@ Feature: UserTeams
46 46 When I visit team merge requests page
47 47 Then I should see merge requests from this team assigned to me
48 48  
  49 + @javascript
49 50 Scenario: I should add user to projects in Team
50 51 Given I have team with projects and members
51 52 Given I have new user "John"
... ...
lib/api/users.rb
... ... @@ -9,7 +9,8 @@ module Gitlab
9 9 # Example Request:
10 10 # GET /users
11 11 get do
12   - @users = paginate User
  12 + @users = User.scoped
  13 + @users = @users.search(params[:search]) if params[:search].present?
13 14 present @users, with: Entities::User
14 15 end
15 16  
... ...
spec/support/select2_helper.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +# Select2 ajax programatic helper
  2 +# It allows you to select value from select2
  3 +#
  4 +# Params
  5 +# value - real value of selected item
  6 +# opts - options containing css selector
  7 +#
  8 +# Usage:
  9 +#
  10 +# select2(2, from: '#user_ids')
  11 +#
  12 +
  13 +module Select2Helper
  14 + def select2(value, options={})
  15 + raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
  16 +
  17 + selector = options[:from]
  18 +
  19 + if options[:multiple]
  20 + page.execute_script("$('#{selector}').select2('val', ['#{value}']);")
  21 + else
  22 + page.execute_script("$('#{selector}').select2('val', '#{value}');")
  23 + end
  24 + end
  25 +end
... ...