diff --git a/Gemfile b/Gemfile
index 73527498..a9f69acc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -113,6 +113,7 @@ group :assets do
gem "therubyracer"
gem 'chosen-rails', "0.9.8"
+ gem 'select2-rails'
gem 'jquery-atwho-rails', "0.1.7"
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
diff --git a/Gemfile.lock b/Gemfile.lock
index f738907f..a19cbfdd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -384,6 +384,9 @@ GEM
seed-fu (2.2.0)
activerecord (~> 3.1)
activesupport (~> 3.1)
+ select2-rails (3.3.1)
+ sass-rails (>= 3.2)
+ thor (~> 0.14)
selenium-webdriver (2.30.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
@@ -534,6 +537,7 @@ DEPENDENCIES
sass-rails (~> 3.2.5)
sdoc
seed-fu
+ select2-rails
settingslogic
shoulda-matchers (= 1.3.0)
sidekiq
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 49effdf9..adb4009f 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -17,6 +17,7 @@
//= require bootstrap
//= require modernizr
//= require chosen-jquery
+//= require select2
//= require raphael
//= require g.raphael-min
//= require g.bar-min
diff --git a/app/assets/javascripts/md5.js b/app/assets/javascripts/md5.js
new file mode 100644
index 00000000..b63716ea
--- /dev/null
+++ b/app/assets/javascripts/md5.js
@@ -0,0 +1,211 @@
+function md5 (str) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
+ // + namespaced by: Michael White (http://getsprink.com)
+ // + tweaked by: Jack
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + input by: Brett Zamir (http://brett-zamir.me)
+ // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // - depends on: utf8_encode
+ // * example 1: md5('Kevin van Zonneveld');
+ // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
+ var xl;
+
+ var rotateLeft = function (lValue, iShiftBits) {
+ return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
+ };
+
+ var addUnsigned = function (lX, lY) {
+ var lX4, lY4, lX8, lY8, lResult;
+ lX8 = (lX & 0x80000000);
+ lY8 = (lY & 0x80000000);
+ lX4 = (lX & 0x40000000);
+ lY4 = (lY & 0x40000000);
+ lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
+ if (lX4 & lY4) {
+ return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
+ }
+ if (lX4 | lY4) {
+ if (lResult & 0x40000000) {
+ return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
+ } else {
+ return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
+ }
+ } else {
+ return (lResult ^ lX8 ^ lY8);
+ }
+ };
+
+ var _F = function (x, y, z) {
+ return (x & y) | ((~x) & z);
+ };
+ var _G = function (x, y, z) {
+ return (x & z) | (y & (~z));
+ };
+ var _H = function (x, y, z) {
+ return (x ^ y ^ z);
+ };
+ var _I = function (x, y, z) {
+ return (y ^ (x | (~z)));
+ };
+
+ var _FF = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _GG = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _HH = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _II = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var convertToWordArray = function (str) {
+ var lWordCount;
+ var lMessageLength = str.length;
+ var lNumberOfWords_temp1 = lMessageLength + 8;
+ var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
+ var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
+ var lWordArray = new Array(lNumberOfWords - 1);
+ var lBytePosition = 0;
+ var lByteCount = 0;
+ while (lByteCount < lMessageLength) {
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
+ lByteCount++;
+ }
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
+ lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
+ lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
+ return lWordArray;
+ };
+
+ var wordToHex = function (lValue) {
+ var wordToHexValue = "",
+ wordToHexValue_temp = "",
+ lByte, lCount;
+ for (lCount = 0; lCount <= 3; lCount++) {
+ lByte = (lValue >>> (lCount * 8)) & 255;
+ wordToHexValue_temp = "0" + lByte.toString(16);
+ wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
+ }
+ return wordToHexValue;
+ };
+
+ var x = [],
+ k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
+ S12 = 12,
+ S13 = 17,
+ S14 = 22,
+ S21 = 5,
+ S22 = 9,
+ S23 = 14,
+ S24 = 20,
+ S31 = 4,
+ S32 = 11,
+ S33 = 16,
+ S34 = 23,
+ S41 = 6,
+ S42 = 10,
+ S43 = 15,
+ S44 = 21;
+
+ str = this.utf8_encode(str);
+ x = convertToWordArray(str);
+ a = 0x67452301;
+ b = 0xEFCDAB89;
+ c = 0x98BADCFE;
+ d = 0x10325476;
+
+ xl = x.length;
+ for (k = 0; k < xl; k += 16) {
+ AA = a;
+ BB = b;
+ CC = c;
+ DD = d;
+ a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
+ d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
+ c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
+ b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
+ a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
+ d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
+ c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
+ b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
+ a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
+ d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
+ c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
+ b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
+ a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
+ d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
+ c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
+ b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
+ a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
+ d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
+ c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
+ b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
+ a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
+ d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
+ c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
+ b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
+ a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
+ d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
+ c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
+ b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
+ a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
+ d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
+ c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
+ b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
+ a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
+ d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
+ c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
+ b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
+ a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
+ d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
+ c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
+ b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
+ a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
+ d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
+ c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
+ b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
+ a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
+ d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
+ c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
+ b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
+ a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
+ d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
+ c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
+ b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
+ a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
+ d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
+ c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
+ b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
+ a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
+ d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
+ c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
+ b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
+ a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
+ d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
+ c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
+ b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
+ a = addUnsigned(a, AA);
+ b = addUnsigned(b, BB);
+ c = addUnsigned(c, CC);
+ d = addUnsigned(d, DD);
+ }
+
+ var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
+
+ return temp.toLowerCase();
+}
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
new file mode 100644
index 00000000..7a39e425
--- /dev/null
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -0,0 +1,50 @@
+$ ->
+ userFormatResult = (user) ->
+ avatar = gon.gravatar_url
+ avatar = avatar.replace('%{hash}', md5(user.email))
+ avatar = avatar.replace('%{size}', '24')
+
+ markup = "
"
+ markup += "
"
+ markup += "
" + user.name + "
"
+ markup += "
" + user.username + "
"
+ markup += "
"
+ markup
+
+ userFormatSelection = (user) ->
+ user.name
+
+ $('.ajax-users-select').select2
+ placeholder: "Search for a user"
+ multiple: $('.ajax-users-select').hasClass('multiselect')
+ minimumInputLength: 0
+ ajax: # instead of writing the function to execute the request we use Select2's convenient helper
+ url: "/api/" + gon.api_version + "/users.json"
+ dataType: "json"
+ data: (term, page) ->
+ search: term # search term
+ per_page: 10
+ private_token: gon.api_token
+
+ results: (data, page) -> # parse the results into the format expected by Select2.
+ # since we are using custom formatting functions we do not need to alter remote JSON data
+ results: data
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ $.ajax(
+ "/api/" + gon.api_version + "/users/" + id + ".json",
+ dataType: "json"
+ data:
+ private_token: gon.api_token
+ ).done (data) ->
+ callback data
+
+
+ formatResult: userFormatResult # omitted for brevity, see the source of this page
+ formatSelection: userFormatSelection # omitted for brevity, see the source of this page
+ dropdownCssClass: "ajax-users-dropdown" # apply css that makes the dropdown taller
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
diff --git a/app/assets/javascripts/utf8_encode.js b/app/assets/javascripts/utf8_encode.js
new file mode 100644
index 00000000..39ffe44d
--- /dev/null
+++ b/app/assets/javascripts/utf8_encode.js
@@ -0,0 +1,70 @@
+function utf8_encode (argString) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + improved by: sowberry
+ // + tweaked by: Jack
+ // + bugfixed by: Onno Marsman
+ // + improved by: Yves Sucaet
+ // + bugfixed by: Onno Marsman
+ // + bugfixed by: Ulrich
+ // + bugfixed by: Rafal Kukawski
+ // + improved by: kirilloid
+ // + bugfixed by: kirilloid
+ // * example 1: utf8_encode('Kevin van Zonneveld');
+ // * returns 1: 'Kevin van Zonneveld'
+
+ if (argString === null || typeof argString === "undefined") {
+ return "";
+ }
+
+ var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+ var utftext = '',
+ start, end, stringl = 0;
+
+ start = end = 0;
+ stringl = string.length;
+ for (var n = 0; n < stringl; n++) {
+ var c1 = string.charCodeAt(n);
+ var enc = null;
+
+ if (c1 < 128) {
+ end++;
+ } else if (c1 > 127 && c1 < 2048) {
+ enc = String.fromCharCode(
+ (c1 >> 6) | 192,
+ ( c1 & 63) | 128
+ );
+ } else if (c1 & 0xF800 != 0xD800) {
+ enc = String.fromCharCode(
+ (c1 >> 12) | 224,
+ ((c1 >> 6) & 63) | 128,
+ ( c1 & 63) | 128
+ );
+ } else { // surrogate pairs
+ if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); }
+ var c2 = string.charCodeAt(++n);
+ if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); }
+ c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
+ enc = String.fromCharCode(
+ (c1 >> 18) | 240,
+ ((c1 >> 12) & 63) | 128,
+ ((c1 >> 6) & 63) | 128,
+ ( c1 & 63) | 128
+ );
+ }
+ if (enc !== null) {
+ if (end > start) {
+ utftext += string.slice(start, end);
+ }
+ utftext += enc;
+ start = end = n + 1;
+ }
+ }
+
+ if (end > start) {
+ utftext += string.slice(start, stringl);
+ }
+
+ return utftext;
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 6b500b88..71068e4a 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -5,6 +5,7 @@
*= require jquery.ui.gitlab
*= require jquery.atwho
*= require chosen
+ *= require select2
*= require_self
*/
@@ -14,7 +15,7 @@
@import "gitlab_bootstrap.scss";
@import "common.scss";
-@import "ref_select.scss";
+@import "selects.scss";
@import "sections/header.scss";
@import "sections/nav.scss";
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index c967c2d1..9afbd8fb 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -554,3 +554,4 @@ img.emoji {
.appear-data {
display: none;
}
+
diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/selects.scss
similarity index 87%
rename from app/assets/stylesheets/ref_select.scss
rename to app/assets/stylesheets/selects.scss
index 284d1c32..a2c99d02 100644
--- a/app/assets/stylesheets/ref_select.scss
+++ b/app/assets/stylesheets/selects.scss
@@ -1,3 +1,23 @@
+.ajax-users-select {
+ width: 400px;
+}
+
+.user-result {
+ .user-image {
+ float: left;
+ }
+ .user-name {
+ }
+ .user-username {
+ color: #999;
+ }
+}
+
+.select2-no-results {
+ padding: 7px;
+ color: #666;
+}
+
/** Branch/tag selector **/
.project-refs-form {
margin: 0;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 6b72f325..32b12466 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -152,5 +152,8 @@ class ApplicationController < ActionController::Base
def add_gon_variables
gon.default_issues_tracker = Project.issues_tracker.default_value
+ gon.api_version = Gitlab::API.version
+ gon.api_token = current_user.private_token if current_user
+ gon.gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
end
end
diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb
index 7de5a68d..ba55648a 100644
--- a/app/controllers/team_members_controller.rb
+++ b/app/controllers/team_members_controller.rb
@@ -16,7 +16,7 @@ class TeamMembersController < ProjectResourceController
end
def create
- users = User.where(id: params[:user_ids])
+ users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]]
diff --git a/app/controllers/teams/members_controller.rb b/app/controllers/teams/members_controller.rb
index 4bd70fd7..f87d422f 100644
--- a/app/controllers/teams/members_controller.rb
+++ b/app/controllers/teams/members_controller.rb
@@ -13,7 +13,7 @@ class Teams::MembersController < Teams::ApplicationController
def create
unless params[:user_ids].blank?
- user_ids = params[:user_ids]
+ user_ids = params[:user_ids].split(',')
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 955dbc17..7567da15 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -169,4 +169,10 @@ module ApplicationHelper
end
alias_method :url_to_image, :image_url
+
+ def users_select_tag(id, opts = {})
+ css_class = "ajax-users-select"
+ css_class << " multiselect" if opts[:multiple]
+ hidden_field_tag(id, '', class: css_class)
+ end
end
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
index 0cb84edd..d5b75851 100644
--- a/app/models/user_team.rb
+++ b/app/models/user_team.rb
@@ -69,6 +69,9 @@ class UserTeam < ActiveRecord::Base
end
def add_members(users, access, group_admin)
+ # reject existing users
+ users.reject! { |id| member_ids.include?(id.to_i) }
+
users.each do |user|
add_member(user, access, group_admin)
end
diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml
index 63bfbecd..4e8f6770 100644
--- a/app/views/team_members/_form.html.haml
+++ b/app/views/team_members/_form.html.haml
@@ -11,7 +11,8 @@
%h6 1. Choose people you want in the team
.clearfix
= f.label :user_ids, "People"
- .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})
+ .input
+ = users_select_tag(:user_ids, multiple: true)
%h6 2. Set access level for them
.clearfix
diff --git a/app/views/teams/members/new.html.haml b/app/views/teams/members/new.html.haml
index 1a03cea0..9b9b3cef 100644
--- a/app/views/teams/members/new.html.haml
+++ b/app/views/teams/members/new.html.haml
@@ -20,7 +20,8 @@
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
- %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_username), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
+ %td
+ = users_select_tag(:user_ids, multiple: true)
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index d106a543..04545a08 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -11,6 +11,7 @@ Feature: Project Team management
Then I should be able to see myself in team
And I should see "Sam" in team list
+ @javascript
Scenario: Add user to project
Given I click link "New Team Member"
And I select "Mike" as "Reporter"
diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb
index 49e9a93f..e8e05435 100644
--- a/features/steps/project/project_team_management.rb
+++ b/features/steps/project/project_team_management.rb
@@ -2,6 +2,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
+ include Select2Helper
Then 'I should be able to see myself in team' do
page.should have_content(@user.name)
@@ -20,8 +21,9 @@ class ProjectTeamManagement < Spinach::FeatureSteps
And 'I select "Mike" as "Reporter"' do
user = User.find_by_name("Mike")
+
+ select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
- select "#{user.name} (#{user.username})", :from => "user_ids"
select "Reporter", :from => "project_access"
end
click_button "Add users"
diff --git a/features/steps/userteams/userteams.rb b/features/steps/userteams/userteams.rb
index e467a170..f0494315 100644
--- a/features/steps/userteams/userteams.rb
+++ b/features/steps/userteams/userteams.rb
@@ -2,238 +2,239 @@ class Userteams < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
+ include Select2Helper
- When 'I do not have teams with me' do
- UserTeam.with_member(current_user).destroy_all
+ When 'I do not have teams with me' do
+ UserTeam.with_member(current_user).destroy_all
+ end
+
+ Then 'I should see dashboard page without teams info block' do
+ page.has_no_css?(".teams-box").must_equal true
+ end
+
+ When 'I have teams with my membership' do
+ team = create :user_team, owner: current_user
+ team.add_member(current_user, UserTeam.access_roles["Master"], true)
+ end
+
+ Then 'I should see dashboard page with teams information block' do
+ page.should have_css(".teams-box")
+ end
+
+ When 'exist user teams' do
+ team = create :user_team
+ team.add_member(current_user, UserTeam.access_roles["Master"], true)
+ end
+
+ And 'I click on "All teams" link' do
+ click_link("All Teams")
+ end
+
+ Then 'I should see "All teams" page' do
+ current_path.should == teams_path
+ end
+
+ And 'I should see exist teams in teams list' do
+ team = UserTeam.last
+ find_in_list(".teams_list tr", team).must_equal true
+ end
+
+ When 'I click to "New team" link' do
+ click_link("New Team")
+ end
+
+ And 'I submit form with new team info' do
+ fill_in 'name', with: 'gitlab'
+
+ fill_in 'user_team_description', with: 'team description'
+ click_button 'Create team'
+ end
+
+ And 'I should see newly created team' do
+ page.should have_content "gitlab"
+ page.should have_content "team description"
+ end
+
+ Then 'I should be redirected to new team page' do
+ team = UserTeam.last
+ current_path.should == team_path(team)
+ end
+
+ When 'I have teams with projects and members' do
+ team = create :user_team, owner: current_user
+ @project = create :project
+ team.add_member(current_user, UserTeam.access_roles["Master"], true)
+ team.assign_to_project(@project, UserTeam.access_roles["Master"])
+ @event = create(:closed_issue_event, project: @project)
+ end
+
+ When 'I visit team page' do
+ visit team_path(UserTeam.last)
+ end
+
+ Then 'I should see projects list' do
+ page.should have_css(".projects_box")
+ projects_box = find(".projects_box")
+ projects_box.should have_content(@project.name)
+ end
+
+ And 'project from team has issues assigned to me' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ project.issues << create(:issue, assignee: current_user)
end
+ end
- Then 'I should see dashboard page without teams info block' do
- page.has_no_css?(".teams-box").must_equal true
- end
+ When 'I visit team issues page' do
+ team = UserTeam.last
+ visit issues_team_path(team)
+ end
- When 'I have teams with my membership' do
- team = create :user_team, owner: current_user
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- end
-
- Then 'I should see dashboard page with teams information block' do
- page.should have_css(".teams-box")
- end
-
- When 'exist user teams' do
- team = create :user_team
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- end
-
- And 'I click on "All teams" link' do
- click_link("All Teams")
- end
-
- Then 'I should see "All teams" page' do
- current_path.should == teams_path
- end
-
- And 'I should see exist teams in teams list' do
- team = UserTeam.last
- find_in_list(".teams_list tr", team).must_equal true
- end
-
- When 'I click to "New team" link' do
- click_link("New Team")
- end
-
- And 'I submit form with new team info' do
- fill_in 'name', with: 'gitlab'
-
- fill_in 'user_team_description', with: 'team description'
- click_button 'Create team'
- end
-
- And 'I should see newly created team' do
- page.should have_content "gitlab"
- page.should have_content "team description"
- end
-
- Then 'I should be redirected to new team page' do
- team = UserTeam.last
- current_path.should == team_path(team)
- end
-
- When 'I have teams with projects and members' do
- team = create :user_team, owner: current_user
- @project = create :project
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- team.assign_to_project(@project, UserTeam.access_roles["Master"])
- @event = create(:closed_issue_event, project: @project)
- end
-
- When 'I visit team page' do
- visit team_path(UserTeam.last)
- end
-
- Then 'I should see projects list' do
- page.should have_css(".projects_box")
- projects_box = find(".projects_box")
- projects_box.should have_content(@project.name)
- end
-
- And 'project from team has issues assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- project.issues << create(:issue, assignee: current_user)
+ Then 'I should see issues from this team assigned to me' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ project.issues.assigned(current_user).each do |issue|
+ page.should have_content issue.title
end
end
+ end
- When 'I visit team issues page' do
- team = UserTeam.last
- visit issues_team_path(team)
+ Given 'I have team with projects and members' do
+ team = create :user_team, owner: current_user
+ project = create :project
+ user = create :user
+ team.add_member(current_user, UserTeam.access_roles["Master"], true)
+ team.add_member(user, UserTeam.access_roles["Developer"], false)
+ team.assign_to_project(project, UserTeam.access_roles["Master"])
+ end
+
+ Given 'project from team has issues assigned to teams members' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ project.issues << create(:issue, assignee: member)
+ end
end
+ end
- Then 'I should see issues from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- project.issues.assigned(current_user).each do |issue|
+ Then 'I should see issues from this team assigned to teams members' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ project.issues.assigned(member).each do |issue|
page.should have_content issue.title
end
end
end
+ end
- Given 'I have team with projects and members' do
- team = create :user_team, owner: current_user
- project = create :project
- user = create :user
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- team.add_member(user, UserTeam.access_roles["Developer"], false)
- team.assign_to_project(project, UserTeam.access_roles["Master"])
+ Given 'project from team has merge requests assigned to me' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ 3.times { project.merge_requests << create(:merge_request, assignee: member) }
+ end
end
+ end
- Given 'project from team has issues assigned to teams members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues << create(:issue, assignee: member)
+ When 'I visit team merge requests page' do
+ team = UserTeam.last
+ visit merge_requests_team_path(team)
+ end
+
+ Then 'I should see merge requests from this team assigned to me' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ project.issues.assigned(member).each do |merge_request|
+ page.should have_content merge_request.title
end
end
end
+ end
- Then 'I should see issues from this team assigned to teams members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |issue|
- page.should have_content issue.title
- end
+ Given 'project from team has merge requests assigned to team members' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ 3.times { project.merge_requests << create(:merge_request, assignee: member) }
+ end
+ end
+ end
+
+ Then 'I should see merge requests from this team assigned to me' do
+ team = UserTeam.last
+ team.projects.each do |project|
+ team.members.each do |member|
+ project.issues.assigned(member).each do |merge_request|
+ page.should have_content merge_request.title
end
end
end
+ end
- Given 'project from team has merge requests assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- 3.times { project.merge_requests << create(:merge_request, assignee: member) }
- end
- end
- end
+ Given 'I have new user "John"' do
+ create :user, name: "John"
+ end
- When 'I visit team merge requests page' do
- team = UserTeam.last
- visit merge_requests_team_path(team)
- end
+ When 'I visit team people page' do
+ team = UserTeam.last
+ visit team_members_path(team)
+ end
- Then 'I should see merge requests from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |merge_request|
- page.should have_content merge_request.title
- end
- end
- end
+ And 'I select user "John" from list with role "Reporter"' do
+ user = User.find_by_name("John")
+ select2(user.id, from: "#user_ids", multiple: true)
+ within "#team_members" do
+ select "Reporter", from: "default_project_access"
end
+ click_button "Add"
+ end
- Given 'project from team has merge requests assigned to team members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- 3.times { project.merge_requests << create(:merge_request, assignee: member) }
- end
- end
- end
+ Then 'I should see user "John" in team list' do
+ user = User.find_by_name("John")
+ team_members_list = find(".team-table")
+ team_members_list.should have_content user.name
+ end
- Then 'I should see merge requests from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |merge_request|
- page.should have_content merge_request.title
- end
- end
- end
- end
+ And 'I have my own project without teams' do
+ @project = create :project, namespace: current_user.namespace
+ end
- Given 'I have new user "John"' do
- create :user, name: "John"
- end
+ And 'I visit my team page' do
+ team = UserTeam.where(owner_id: current_user.id).last
+ visit team_path(team)
+ end
- When 'I visit team people page' do
- team = UserTeam.last
- visit team_members_path(team)
- end
+ When 'I click on link "Projects"' do
+ click_link "Projects"
+ end
- And 'I select user "John" from list with role "Reporter"' do
- user = User.find_by_name("John")
- within "#team_members" do
- select "#{user.name} (#{user.username})", from: "user_ids"
- select "Reporter", from: "default_project_access"
- end
- click_button "Add"
- end
+ And 'I click link "Assign project to Team"' do
+ click_link "Assign project to Team"
+ end
- Then 'I should see user "John" in team list' do
- user = User.find_by_name("John")
- team_members_list = find(".team-table")
- team_members_list.should have_content user.name
- end
+ Then 'I should see form with my own project in avaliable projects list' do
+ projects_select = find("#project_ids")
+ projects_select.should have_content(@project.name)
+ end
- And 'I have my own project without teams' do
- @project = create :project, namespace: current_user.namespace
+ When 'I submit form with selected project and max access' do
+ within "#assign_projects" do
+ select @project.name_with_namespace, from: "project_ids"
+ select "Reporter", from: "greatest_project_access"
end
+ click_button "Add"
+ end
- And 'I visit my team page' do
- team = UserTeam.where(owner_id: current_user.id).last
- visit team_path(team)
- end
+ Then 'I should see my own project in team projects list' do
+ projects = find(".projects-table")
+ projects.should have_content(@project.name)
+ end
- When 'I click on link "Projects"' do
- click_link "Projects"
- end
-
- And 'I click link "Assign project to Team"' do
- click_link "Assign project to Team"
- end
-
- Then 'I should see form with my own project in avaliable projects list' do
- projects_select = find("#project_ids")
- projects_select.should have_content(@project.name)
- end
-
- When 'I submit form with selected project and max access' do
- within "#assign_projects" do
- select @project.name_with_namespace, from: "project_ids"
- select "Reporter", from: "greatest_project_access"
- end
- click_button "Add"
- end
-
- Then 'I should see my own project in team projects list' do
- projects = find(".projects-table")
- projects.should have_content(@project.name)
- end
-
- When 'I click link "New Team Member"' do
- click_link "New Team Member"
- end
+ When 'I click link "New Team Member"' do
+ click_link "New Team Member"
+ end
protected
@@ -257,5 +258,4 @@ class Userteams < Spinach::FeatureSteps
end
entered
end
-
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 03521afb..6f1e4df3 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -14,7 +14,7 @@ require 'spinach/capybara'
require 'sidekiq/testing/inline'
-%w(stubbed_repository valid_commit).each do |f|
+%w(stubbed_repository valid_commit select2_helper).each do |f|
require Rails.root.join('spec', 'support', f)
end
diff --git a/features/teams/team.feature b/features/teams/team.feature
index e15e3f06..5b7c15a8 100644
--- a/features/teams/team.feature
+++ b/features/teams/team.feature
@@ -46,6 +46,7 @@ Feature: UserTeams
When I visit team merge requests page
Then I should see merge requests from this team assigned to me
+ @javascript
Scenario: I should add user to projects in Team
Given I have team with projects and members
Given I have new user "John"
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 6cc3a7e5..1462e7b4 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -9,7 +9,8 @@ module Gitlab
# Example Request:
# GET /users
get do
- @users = paginate User
+ @users = User.scoped
+ @users = @users.search(params[:search]) if params[:search].present?
present @users, with: Entities::User
end
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
new file mode 100644
index 00000000..20dd9bf4
--- /dev/null
+++ b/spec/support/select2_helper.rb
@@ -0,0 +1,25 @@
+# Select2 ajax programatic helper
+# It allows you to select value from select2
+#
+# Params
+# value - real value of selected item
+# opts - options containing css selector
+#
+# Usage:
+#
+# select2(2, from: '#user_ids')
+#
+
+module Select2Helper
+ def select2(value, options={})
+ raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
+
+ selector = options[:from]
+
+ if options[:multiple]
+ page.execute_script("$('#{selector}').select2('val', ['#{value}']);")
+ else
+ page.execute_script("$('#{selector}').select2('val', '#{value}');")
+ end
+ end
+end