summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG4
-rw-r--r--app/assets/javascripts/api.js.coffee67
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/notes.js.coffee1
-rw-r--r--app/assets/javascripts/project_users_select.js.coffee56
-rw-r--r--app/assets/javascripts/users_select.js.coffee71
-rw-r--r--app/assets/stylesheets/generic/filters.scss25
-rw-r--r--app/assets/stylesheets/generic/selects.scss5
-rw-r--r--app/assets/stylesheets/pages/issues.scss12
-rw-r--r--app/controllers/autocomplete_controller.rb30
-rw-r--r--app/finders/issuable_finder.rb8
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/labels_helper.rb4
-rw-r--r--app/helpers/milestones_helper.rb11
-rw-r--r--app/helpers/selects_helper.rb29
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/views/projects/_issuable_form.html.haml4
-rw-r--r--app/views/projects/issues/_issue_context.html.haml4
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/update.js.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml2
-rw-r--r--app/views/projects/merge_requests/update.js.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/shared/_issuable_filter.html.haml127
-rw-r--r--config/routes.rb5
-rw-r--r--doc/ssh/README.md2
-rw-r--r--features/dashboard/issues.feature2
-rw-r--r--features/dashboard/merge_requests.feature2
-rw-r--r--features/project/issues/filter_labels.feature6
-rw-r--r--features/steps/dashboard/issues.rb17
-rw-r--r--features/steps/dashboard/merge_requests.rb17
-rw-r--r--features/steps/project/issues/filter_labels.rb23
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb51
-rw-r--r--spec/features/issues_spec.rb4
-rw-r--r--spec/support/select2_helper.rb4
37 files changed, 304 insertions, 319 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 242d2c773c6..04d889456dd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -43,6 +43,10 @@ v 7.10.0 (unreleased)
- Link note avatar to user.
- Make Git-over-SSH errors more descriptive.
- Fix EmailsOnPush.
+ - Refactor issue filtering
+ - AJAX selectbox for issue assignee and author filters
+ - Fix issue with missing options in issue filtering dropdown if selected one
+ - Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
v 7.9.0
- Send EmailsOnPush email when branch or tag is created or deleted.
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 27d04e7cac6..9e5d594c861 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -1,57 +1,7 @@
@Api =
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
- users_path: "/api/:version/users.json"
- user_path: "/api/:version/users/:id.json"
- notes_path: "/api/:version/projects/:id/notes.json"
namespaces_path: "/api/:version/namespaces.json"
- project_users_path: "/api/:version/projects/:id/users.json"
-
- # Get 20 (depends on api) recent notes
- # and sort the ascending from oldest to newest
- notes: (project_id, callback) ->
- url = Api.buildUrl(Api.notes_path)
- url = url.replace(':id', project_id)
-
- $.ajax(
- url: url,
- data:
- private_token: gon.api_token
- gfm: true
- recent: true
- dataType: "json"
- ).done (notes) ->
- notes.sort (a, b) ->
- return a.id - b.id
- callback(notes)
-
- user: (user_id, callback) ->
- url = Api.buildUrl(Api.user_path)
- url = url.replace(':id', user_id)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- dataType: "json"
- ).done (user) ->
- callback(user)
-
- # Return users list. Filtered by query
- # Only active users retrieved
- users: (query, callback) ->
- url = Api.buildUrl(Api.users_path)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- active: true
- dataType: "json"
- ).done (users) ->
- callback(users)
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -80,23 +30,6 @@
).done (groups) ->
callback(groups)
- # Return project users list. Filtered by query
- # Only active users retrieved
- projectUsers: (project_id, query, callback) ->
- url = Api.buildUrl(Api.project_users_path)
- url = url.replace(':id', project_id)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- active: true
- dataType: "json"
- ).done (users) ->
- callback(users)
-
# Return namespaces list. Filtered by query
namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 3535d8c2cfc..821712f7512 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -127,7 +127,7 @@ class Dispatcher
when 'show'
new ProjectShow()
when 'issues', 'merge_requests'
- new ProjectUsersSelect()
+ new UsersSelect()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index c366c98cf54..b61c4dd6544 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -57,6 +57,7 @@ class @Notes
@notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
# Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
$(document).on('keydown', @notes_forms, (e) ->
+ return if e.originalEvent.repeat
if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
$(@).parents('form').submit()
)
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
deleted file mode 100644
index 80ab1a61ab9..00000000000
--- a/app/assets/javascripts/project_users_select.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-class @ProjectUsersSelect
- constructor: ->
- $('.ajax-project-users-select').each (i, select) =>
- project_id = $(select).data('project-id') || $('body').data('project-id')
-
- $(select).select2
- placeholder: $(select).data('placeholder') || "Search for a user"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.projectUsers project_id, query.term, (users) ->
- data = { results: users }
-
- if query.term.length == 0
- nullUser = {
- name: 'Unassigned',
- avatar: null,
- username: 'none',
- id: -1
- }
-
- data.results.unshift(nullUser)
-
- query.callback(data)
-
- initSelection: (element, callback) ->
- id = $(element).val()
- if id != "" && id != "-1"
- Api.user(id, callback)
-
-
- formatResult: (args...) =>
- @formatResult(args...)
- formatSelection: (args...) =>
- @formatSelection(args...)
- dropdownCssClass: "ajax-project-users-dropdown"
- dropdownAutoWidth: true
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
-
- formatResult: (user) ->
- if user.avatar_url
- avatar = user.avatar_url
- else
- avatar = gon.default_avatar_url
-
- avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>"
-
- "<div class='user-result'>
- #{avatarMarkup}
- <div class='user-name'>#{user.name}</div>
- <div class='user-username'>#{user.username}</div>
- </div>"
-
- formatSelection: (user) ->
- user.name
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 9eee7406511..f464067686e 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,20 +1,48 @@
class @UsersSelect
constructor: ->
+ @usersPath = "/autocomplete/users.json"
+ @userPath = "/autocomplete/users/:id.json"
+
$('.ajax-users-select').each (i, select) =>
+ @projectId = $(select).data('project-id')
+ @groupId = $(select).data('group-id')
+ showNullUser = $(select).data('null-user')
+ showAnyUser = $(select).data('any-user')
+
$(select).select2
placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
- query: (query) ->
- Api.users query.term, (users) ->
+ query: (query) =>
+ @users query.term, (users) =>
data = { results: users }
+
+ if query.term.length == 0
+ anyUser = {
+ name: 'Any',
+ avatar: null,
+ username: 'none',
+ id: null
+ }
+
+ nullUser = {
+ name: 'Unassigned',
+ avatar: null,
+ username: 'none',
+ id: 0
+ }
+
+ if showNullUser
+ data.results.unshift(nullUser)
+ if showAnyUser
+ data.results.unshift(anyUser)
+
query.callback(data)
- initSelection: (element, callback) ->
+ initSelection: (element, callback) =>
id = $(element).val()
- if id isnt ""
- Api.user(id, callback)
-
+ if id != "" && id != "0"
+ @user(id, callback)
formatResult: (args...) =>
@formatResult(args...)
@@ -38,3 +66,34 @@ class @UsersSelect
formatSelection: (user) ->
user.name
+
+ user: (user_id, callback) =>
+ url = @buildUrl(@userPath)
+ url = url.replace(':id', user_id)
+
+ $.ajax(
+ url: url
+ dataType: "json"
+ ).done (user) ->
+ callback(user)
+
+ # Return users list. Filtered by query
+ # Only active users retrieved
+ users: (query, callback) =>
+ url = @buildUrl(@usersPath)
+
+ $.ajax(
+ url: url
+ data:
+ search: query
+ per_page: 20
+ active: true
+ project_id: @projectId
+ group_id: @groupId
+ dataType: "json"
+ ).done (users) ->
+ callback(users)
+
+ buildUrl: (url) ->
+ url = gon.relative_url_root + url if gon.relative_url_root?
+ return url
diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/generic/filters.scss
new file mode 100644
index 00000000000..20c6a85de98
--- /dev/null
+++ b/app/assets/stylesheets/generic/filters.scss
@@ -0,0 +1,25 @@
+.filter-item {
+ margin-right: 15px;
+}
+
+.issues-state-filters {
+ li.active a,
+ li.active a:hover {
+ background: #f5f5f5;
+ border-bottom: 1px solid #f5f5f5 !important;
+ }
+}
+
+.issues-details-filters {
+ font-size: 13px;
+ background: #f5f5f5;
+ margin: -10px 0;
+ padding: 10px 15px;
+ margin-top: -15px;
+ border-left: 1px solid #DDD;
+ border-right: 1px solid #DDD;
+
+ .btn {
+ font-size: 13px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index 7557f411111..69613608c82 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -28,6 +28,7 @@
.select2-drop-active {
border: 1px solid #BBB !important;
margin-top: 4px;
+ font-size: 13px;
&.select2-drop-above {
margin-bottom: 8px;
@@ -106,3 +107,7 @@
font-weight: bolder;
}
}
+
+.ajax-users-dropdown {
+ min-width: 225px !important;
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6c1dd4f7e9f..b8ad26d69cd 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -41,12 +41,9 @@
}
.check-all-holder {
- height: 36px;
+ line-height: 36px;
float: left;
- margin-right: 12px;
- padding: 6px 15px;
- border: 1px solid #ccc;
- @include border-radius(4px);
+ margin-right: 15px;
}
.issues_content {
@@ -60,6 +57,7 @@
}
@media (min-width: 800px) {
+ .issues-filters,
.issues_bulk_update {
select, .select2-container {
width: 120px !important;
@@ -69,14 +67,16 @@
}
@media (min-width: 1200px) {
+ .issues-filters,
.issues_bulk_update {
select, .select2-container {
- width: 160px !important;
+ width: 150px !important;
display: inline-block;
}
}
}
+.issues-filters,
.issues_bulk_update {
.select2-container .select2-choice {
color: #444 !important;
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
new file mode 100644
index 00000000000..11af9895261
--- /dev/null
+++ b/app/controllers/autocomplete_controller.rb
@@ -0,0 +1,30 @@
+class AutocompleteController < ApplicationController
+ def users
+ @users =
+ if params[:project_id].present?
+ project = Project.find(params[:project_id])
+
+ if can?(current_user, :read_project, project)
+ project.team.users
+ end
+ elsif params[:group_id]
+ group = Group.find(params[:group_id])
+
+ if can?(current_user, :read_group, group)
+ group.users
+ end
+ else
+ User.all
+ end
+
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = @users.active
+ @users = @users.page(params[:page]).per(PER_PAGE)
+ render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
+ end
+
+ def user
+ @user = User.find(params[:id])
+ render json: @user, only: [:name, :username, :id], methods: [:avatar_url]
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 088a766ed3a..2c0702073d4 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,6 +19,8 @@
require_relative 'projects_finder'
class IssuableFinder
+ NONE = '0'
+
attr_accessor :current_user, :params
def execute(current_user, params)
@@ -112,7 +114,7 @@ class IssuableFinder
def by_milestone(items)
if params[:milestone_id].present?
- items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
+ items = items.where(milestone_id: (params[:milestone_id] == NONE ? nil : params[:milestone_id]))
end
items
@@ -120,7 +122,7 @@ class IssuableFinder
def by_assignee(items)
if params[:assignee_id].present?
- items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
+ items = items.where(assignee_id: (params[:assignee_id] == NONE ? nil : params[:assignee_id]))
end
items
@@ -128,7 +130,7 @@ class IssuableFinder
def by_author(items)
if params[:author_id].present?
- items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
+ items = items.where(author_id: (params[:author_id] == NONE ? nil : params[:author_id]))
end
items
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 38b5fc4a011..3f3509bb18a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -275,7 +275,9 @@ module ApplicationHelper
'https://' + promo_host
end
- def page_filter_path(options={})
+ def page_filter_path(options = {})
+ without = options.delete(:without)
+
exist_opts = {
state: params[:state],
scope: params[:scope],
@@ -288,6 +290,12 @@ module ApplicationHelper
options = exist_opts.merge(options)
+ if without.present?
+ without.each do |key|
+ options.delete(key)
+ end
+ end
+
path = request.path
path << "?#{options.to_param}"
path
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 49063491abf..32ef2e7ca84 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -47,4 +47,8 @@ module LabelsHelper
"#FFF"
end
end
+
+ def project_labels_options(project)
+ options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
+ end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 59fdc0d49cc..282bdf744d2 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -19,4 +19,15 @@ module MilestonesHelper
content_tag :div, nil, options
end
end
+
+ def projects_milestones_options
+ milestones =
+ if @project
+ @project.milestones
+ else
+ Milestone.where(project_id: @projects)
+ end.active
+
+ options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
+ end
end
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 796d805f219..457cd3fa46b 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -4,18 +4,27 @@ module SelectsHelper
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
+ placeholder = opts[:placeholder] || 'Search for a user'
- hidden_field_tag(id, value, class: css_class)
- end
+ null_user = opts[:null_user] || false
+ any_user = opts[:any_user] || false
- def project_users_select_tag(id, opts = {})
- css_class = "ajax-project-users-select "
- css_class << "multiselect " if opts[:multiple]
- css_class << (opts[:class] || '')
- value = opts[:selected] || ''
- placeholder = opts[:placeholder] || 'Select user'
- project_id = opts[:project_id] || @project.id
- hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
+ html = {
+ class: css_class,
+ 'data-placeholder' => placeholder,
+ 'data-null-user' => null_user,
+ 'data-any-user' => any_user,
+ }
+
+ unless opts[:scope] == :all
+ if @project
+ html['data-project-id'] = @project.id
+ elsif @group
+ html['data-group-id'] = @group.id
+ end
+ end
+
+ hidden_field_tag(id, value, html)
end
def groups_select_tag(id, opts = {})
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 3371fe7d5ef..8f04a69287a 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -14,8 +14,8 @@ module Issues
issue.update_nth_task(params[:task_num].to_i, false)
end
- params[:assignee_id] = "" if params[:assignee_id] == "-1"
- params[:milestone_id] = "" if params[:milestone_id] == "-1"
+ params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
+ params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
old_labels = issue.labels.to_a
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 0ac6dfea6fd..23af2656c37 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -23,8 +23,8 @@ module MergeRequests
merge_request.update_nth_task(params[:task_num].to_i, false)
end
- params[:assignee_id] = "" if params[:assignee_id] == "-1"
- params[:milestone_id] = "" if params[:milestone_id] == "-1"
+ params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
+ params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
old_labels = merge_request.labels.to_a
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 7fd5fe8a6e1..e321a84974e 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -35,8 +35,8 @@
%i.fa.fa-user
Assign to
.col-sm-10
- = project_users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select a user', class: 'custom-form-control',
+ = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
+ placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index c3d6dc2e50b..52e38050419 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -8,7 +8,7 @@
- else
none
- if can?(current_user, :modify_issue, @issue)
- = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
+ = users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id, null_user: true)
%div.prepend-top-20.clearfix
.issuable-context-title
@@ -44,5 +44,3 @@
:coffeescript
new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}")
-
-
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 54e3009cca2..210b77a6b15 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -19,7 +19,7 @@
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
= select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
- = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
+ = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true)
= select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 82c0e653759..1d38662bff8 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -13,5 +13,5 @@
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$('.edit-issue.inline-update input[type="submit"]').hide();
-new ProjectUsersSelect();
+new UsersSelect()
new Issue();
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 1d8eef4e8ce..d986ce67c0c 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -39,7 +39,7 @@
%i.fa.fa-user
Assign to
.col-sm-10
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
+ = users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
index 80e5c223d60..105562fb05e 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -9,7 +9,7 @@
none
.issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request)
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
+ = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, null_user: true)
%div.prepend-top-20.clearfix
.issuable-context-title
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index f5cc98c7fa4..b4df1d20737 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -2,7 +2,7 @@
$('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
$('.context').effect('highlight');
- new ProjectUsersSelect();
+ new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
merge_request = new MergeRequest();
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 0f824bdabf8..5daae2708e6 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,7 +1,7 @@
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
.form-group
= f.label :user_ids, "People", class: 'control-label'
- .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
+ .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all)
.form-group
= f.label :access_level, "Project Access", class: 'control-label'
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
index 5412b9ef0f4..77085ccb56b 100644
--- a/app/views/shared/_issuable_filter.html.haml
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -14,106 +14,35 @@
%i.fa.fa-compass
All
- %div
- - if controller.controller_name == 'issues'
- .check-all-holder
- = check_box_tag "check_all_issues", nil, false,
- class: "check_all_issues left",
- disabled: !can?(current_user, :modify_issue, @project)
- .issues-other-filters
- .dropdown.inline.assignee-filter
- %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to page_filter_path(assignee_id: nil) do
- Any
- = link_to page_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to page_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
+ .issues-details-filters
+ = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_id, :label_name]), method: :get, class: 'filter-form' do
+ - if controller.controller_name == 'issues'
+ .check-all-holder
+ = check_box_tag "check_all_issues", nil, false,
+ class: "check_all_issues left",
+ disabled: !can?(current_user, :modify_issue, @project)
+ .issues-other-filters
+ .filter-item.inline
+ = users_select_tag(:assignee_id, selected: params[:assignee_id],
+ placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true)
- .dropdown.inline.prepend-left-10.author-filter
- %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light author:
- - if @author.present?
- %strong= @author.name
- - elsif params[:author_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to page_filter_path(author_id: nil) do
- Any
- = link_to page_filter_path(author_id: 0) do
- Unassigned
- - @authors.sort_by(&:name).each do |user|
- %li
- = link_to page_filter_path(author_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
+ .filter-item.inline
+ = users_select_tag(:author_id, selected: params[:author_id],
+ placeholder: 'Author', class: 'trigger-submit', any_user: true)
- .dropdown.inline.prepend-left-10.milestone-filter
- %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to page_filter_path(milestone_id: nil) do
- Any
- = link_to page_filter_path(milestone_id: 0) do
- None (backlog)
- - @milestones.each do |milestone|
- %li
- = link_to page_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
+ .filter-item.inline.milestone-filter
+ = select_tag('milestone_id', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone')
- - if @project
- .dropdown.inline.prepend-left-10.labels-filter
- %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
- %i.fa.fa-tags
- %span.light label:
- - if params[:label_name].present?
- %strong= params[:label_name]
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to page_filter_path(label_name: nil) do
- Any
- - if @project.labels.any?
- - @project.labels.each do |label|
- %li
- = link_to page_filter_path(label_name: label.name) do
- = render_colored_label(label)
- - else
- %li
- = link_to generate_namespace_project_labels_path(@project.namespace, @project, redirect: request.original_url), method: :post do
- %i.fa.fa-plus-circle
- Create default labels
+ - if @project
+ .filter-item.inline.labels-filter
+ = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label')
- .pull-right
- = render 'shared/sort_dropdown'
+ .pull-right
+ = render 'shared/sort_dropdown'
+
+:coffeescript
+ new UsersSelect()
+
+ $('form.filter-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '&' + $(@).serialize()
diff --git a/config/routes.rb b/config/routes.rb
index c30cd768572..388858d2670 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -8,6 +8,11 @@ Gitlab::Application.routes.draw do
authorizations: 'oauth/authorizations'
end
+ # Autocomplete
+ get '/autocomplete/users' => 'autocomplete#users'
+ get '/autocomplete/users/:id' => 'autocomplete#user'
+
+
# Search
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 66941521c2e..0acb15896d3 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -68,6 +68,6 @@ You can't add the same deploy key twice with the 'New Deploy Key' option.
If you want to add the same key to another project, please enable it in the
list that says 'Deploy keys from projects available to you'. All the deploy
keys of all the projects you have access to are available. This project
-access can happen through being a direct member of the projecti, or through
+access can happen through being a direct member of the project, or through
a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more
information.
diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature
index 72627e43e05..99dad88a402 100644
--- a/features/dashboard/issues.feature
+++ b/features/dashboard/issues.feature
@@ -10,10 +10,12 @@ Feature: Dashboard Issues
Scenario: I should see assigned issues
Then I should see issues assigned to me
+ @javascript
Scenario: I should see authored issues
When I click "Authored by me" link
Then I should see issues authored by me
+ @javascript
Scenario: I should see all issues
When I click "All" link
Then I should see all issues
diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature
index dcef1290e7e..4a2c997d707 100644
--- a/features/dashboard/merge_requests.feature
+++ b/features/dashboard/merge_requests.feature
@@ -10,10 +10,12 @@ Feature: Dashboard Merge Requests
Scenario: I should see assigned merge_requests
Then I should see merge requests assigned to me
+ @javascript
Scenario: I should see authored merge_requests
When I click "Authored by me" link
Then I should see merge requests authored by me
+ @javascript
Scenario: I should see all merge_requests
When I click "All" link
Then I should see all merge requests
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
index 2c69a78a749..e316f519861 100644
--- a/features/project/issues/filter_labels.feature
+++ b/features/project/issues/filter_labels.feature
@@ -8,11 +8,7 @@ Feature: Project Issues Filter Labels
And project "Shop" has issue "Feature1" with labels: "feature"
Given I visit project "Shop" issues page
- Scenario: I should see project issues
- Then I should see "bug" in labels filter
- And I should see "feature" in labels filter
- And I should see "enhancement" in labels filter
-
+ @javascript
Scenario: I filter by one label
Given I click link "bug"
Then I should see "Bugfix1" in issues list
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index b77113e3974..60da36e86de 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -1,6 +1,7 @@
class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
step 'I should see issues assigned to me' do
should_see(assigned_issue)
@@ -35,21 +36,13 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".assignee-filter" do
- click_link "Any"
- end
- within ".author-filter" do
- click_link current_user.name
- end
+ select2(current_user.id, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
step 'I click "All" link' do
- within ".author-filter" do
- click_link "Any"
- end
- within ".assignee-filter" do
- click_link "Any"
- end
+ select2(nil, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
def should_see(issue)
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 6261c89924c..9d92082bb83 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -1,6 +1,7 @@
class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request)
@@ -39,21 +40,13 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".assignee-filter" do
- click_link "Any"
- end
- within ".author-filter" do
- click_link current_user.name
- end
+ select2(current_user.id, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
step 'I click "All" link' do
- within ".author-filter" do
- click_link "Any"
- end
- within ".assignee-filter" do
- click_link "Any"
- end
+ select2(nil, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
def should_see(merge_request)
diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index e62fa9c84c8..5740bd12837 100644
--- a/features/steps/project/issues/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -2,24 +2,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
-
- step 'I should see "bug" in labels filter' do
- within ".labels-filter" do
- page.should have_content "bug"
- end
- end
-
- step 'I should see "feature" in labels filter' do
- within ".labels-filter" do
- page.should have_content "feature"
- end
- end
-
- step 'I should see "enhancement" in labels filter' do
- within ".labels-filter" do
- page.should have_content "enhancement"
- end
- end
+ include Select2Helper
step 'I should see "Bugfix1" in issues list' do
within ".issues-list" do
@@ -46,9 +29,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
end
step 'I click link "bug"' do
- within ".labels-filter" do
- click_link "bug"
- end
+ select2('bug', from: "#label_name")
end
step 'I click link "feature"' do
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
new file mode 100644
index 00000000000..a0909cec3bd
--- /dev/null
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe AutocompleteController do
+ let!(:project) { create(:project) }
+ let!(:user) { create(:user) }
+ let!(:user2) { create(:user) }
+
+ context 'project members' do
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+
+ get(:users, project_id: project.id)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it { body.should be_kind_of(Array) }
+ it { body.size.should eq(1) }
+ it { body.first["username"].should == user.username }
+ end
+
+ context 'group members' do
+ let(:group) { create(:group) }
+
+ before do
+ sign_in(user)
+ group.add_owner(user)
+
+ get(:users, group_id: group.id)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it { body.should be_kind_of(Array) }
+ it { body.size.should eq(1) }
+ it { body.first["username"].should == user.username }
+ end
+
+ context 'all users' do
+ before do
+ sign_in(user)
+ get(:users)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it { body.should be_kind_of(Array) }
+ it { body.size.should eq(User.count) }
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index a2db57ad908..e5f33d5a25a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -95,7 +95,7 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
it 'should allow filtering by issues with no specified milestone' do
- visit namespace_project_issues_path(project.namespace, project, milestone_id: '0')
+ visit namespace_project_issues_path(project.namespace, project, milestone_id: IssuableFinder::NONE)
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
@@ -111,7 +111,7 @@ describe 'Issues', feature: true do
end
it 'should allow filtering by issues with no specified assignee' do
- visit namespace_project_issues_path(project.namespace, project, assignee_id: '0')
+ visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
index c7cf109a7bb..691f84f39d4 100644
--- a/spec/support/select2_helper.rb
+++ b/spec/support/select2_helper.rb
@@ -17,9 +17,9 @@ module Select2Helper
selector = options[:from]
if options[:multiple]
- execute_script("$('#{selector}').select2('val', ['#{value}']);")
+ execute_script("$('#{selector}').select2('val', ['#{value}'], true);")
else
- execute_script("$('#{selector}').select2('val', '#{value}');")
+ execute_script("$('#{selector}').select2('val', '#{value}', true);")
end
end
end