diff --git a/Gemfile b/Gemfile index 73527498c7818b2ba02356a438e80730840f30d4..a9f69acc5753f735e328d0bece52f4861bfe2e9c 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 f738907fd420b15523b3efaec1be1ed534be9ec1..a19cbfdd90a963c00375c9d87e7fff5723b77fa4 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 49effdf9c152a9cf2ce3fd613cade95372a3f4db..adb4009fbc2614ecf0beceddad37a63475dadb8d 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 0000000000000000000000000000000000000000..b63716eaad2f8f90678b99ccac2eb7f226214847 --- /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 0000000000000000000000000000000000000000..7a39e4256e241d1464363f5800a67de641d18bbe --- /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 0000000000000000000000000000000000000000..39ffe44dae0ef78c6e22dbe5f0d60bb3265c94af --- /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 6b500b88823a7be2a3f2e11a48509467fbcd1faf..71068e4ae95f4b5b9e4a44b756583eb45f86e2e6 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 c967c2d1c1714e91a1115f426fe0ae1eaec71e42..9afbd8fb53cc81de3e27b3fc84876836878ef53d 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 284d1c324e5f2ac4381931be6515a85de0a4fec9..a2c99d0230344a9b774723835e40d0c59cbe2549 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 6b72f3252042b2c118ebc15fe56e9ebf183a4bb2..32b1246601de7fbd3eca5fce5938fdb29a352713 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 7de5a68d55ad24d9572d458c944607ce2077ae76..ba55648adad1666714316353618181520cecb25c 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 4bd70fd7247e6b020b9039738b0f78f1e9bccc15..f87d422f84edbe3374f578c96e0679bc5a82802d 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 955dbc1725499920c7670faf9d02ec9b1e90e577..7567da15f9ea7206b358544c0b7f0c430de527ad 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 0cb84edd66d5277ded9eb08e89b4ebc0ce02d881..d5b758512792598e6809c01bad1c2b9631ad274b 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 63bfbecdc283fad0aeea462752db4753ce39e467..4e8f67702cf6f9557b9988ca09a68b06f61dcdcf 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 1a03cea05cc10320f84475f063fa1e87b189c95b..9b9b3cef59bcabaa92b553660971800531ddb2e9 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 d106a543e80bb6df1dbdc18c38be334aa13e21cd..04545a08e0ab051f332d3a5dd0619b075e919a8e 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 49e9a93fb43cadca591c2b7497e29fb3017e1321..e8e054350dd1409516c5e080c106624e1752650b 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 e467a17095118a94fc3ecc6a73386cca33485604..f0494315767b8718ef56cb6d63d4ea9e144e4347 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 - end + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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' + 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 + 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 + 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 + 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 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 + 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 + 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 + 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 - When 'I visit team issues page' do - team = UserTeam.last - visit issues_team_path(team) - end + When 'I visit team issues page' do + team = UserTeam.last + visit issues_team_path(team) + 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| - page.should have_content issue.title - 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| + page.should have_content issue.title 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"]) - 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"]) + 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 + 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 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 + 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 '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 + 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 - When 'I visit team merge requests page' do - team = UserTeam.last - visit merge_requests_team_path(team) - end + 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 + 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 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 + 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 + 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 'I have new user "John"' do - create :user, name: "John" - end + Given 'I have new user "John"' do + create :user, name: "John" + end - When 'I visit team people page' do - team = UserTeam.last - visit team_members_path(team) - end + When 'I visit team people page' do + team = UserTeam.last + visit team_members_path(team) + 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" + 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 - 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 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 - And 'I have my own project without teams' do - @project = create :project, namespace: current_user.namespace - end + And 'I have my own project without teams' do + @project = create :project, namespace: current_user.namespace + end - And 'I visit my team page' do - team = UserTeam.where(owner_id: current_user.id).last - visit team_path(team) - end + And 'I visit my team page' do + team = UserTeam.where(owner_id: current_user.id).last + visit team_path(team) + end - When 'I click on link "Projects"' do - click_link "Projects" - 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 + 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 + 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" + 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 + 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 03521afb9a176419c3886749fd6b107f2c493592..6f1e4df39fcf2ded40990ddfc45cda5654f26f1f 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 e15e3f0640d08800f0321696ca3ef17996699a89..5b7c15a86e562f8772078e0f7c3199dd6d705bff 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 6cc3a7e52c9455a6970daab6c97ddd90a7ee4f8f..1462e7b4c1d2fdbdf7b4c4e7249e80a77d2e4fd3 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 0000000000000000000000000000000000000000..20dd9bf437c77bf771a8433ade8962924a03cad3 --- /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