diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index e95123c0933..059cf160fa2 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -12,6 +12,7 @@ class Groups::LabelsController < Groups::ApplicationController format.html do @labels = @group.labels .optionally_search(params[:search]) + .order_by(sort) .page(params[:page]) end format.json do @@ -117,4 +118,8 @@ class Groups::LabelsController < Groups::ApplicationController include_descendant_groups: params[:include_descendant_groups], search: params[:search]).execute end + + def sort + @sort ||= params[:sort] || 'name_asc' + end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 8a2bce6e7b5..69332ee2a0e 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -163,7 +163,12 @@ class Projects::LabelsController < Projects::ApplicationController LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups], - search: params[:search]).execute + search: params[:search], + sort: sort).execute + end + + def sort + @sort ||= params[:sort] || 'name_asc' end def authorize_admin_labels! diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 1d05bf28438..8418577dab2 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -54,7 +54,11 @@ class LabelsFinder < UnionFinder end def sort(items) - items.reorder(title: :asc) + if params[:sort] + items.order_by(params[:sort]) + else + items.reorder(title: :asc) + end end def with_title(items) diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 731b6806b5f..a6e65d30eda 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -101,6 +101,17 @@ module SortingHelper } end + def label_sort_options_hash + { + sort_value_name => sort_title_name, + sort_value_name_desc => sort_title_name_desc, + sort_value_recently_created => sort_title_recently_created, + sort_value_oldest_created => sort_title_oldest_created, + sort_value_recently_updated => sort_title_recently_updated, + sort_value_oldest_updated => sort_title_oldest_updated + } + end + def sortable_item(item, path, sorted_by) link_to item, path, class: sorted_by == item ? 'is-active' : '' end diff --git a/app/models/label.rb b/app/models/label.rb index 8db7c3abd10..8dc7ded53ad 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -6,6 +6,7 @@ class Label < ActiveRecord::Base include Subscribable include Gitlab::SQL::Pattern include OptionallySearch + include Sortable # Represents a "No Label" state used for filtering Issues and Merge # Requests that have no label assigned. @@ -41,6 +42,8 @@ class Label < ActiveRecord::Base scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) } scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) } scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) } + scope :order_name_asc, -> { reorder(title: :asc) } + scope :order_name_desc, -> { reorder(title: :desc) } def self.prioritized(project) joins(:priorities) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index e6821009d03..86178eb2ffd 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -22,6 +22,7 @@ %span.input-group-append %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } = icon("search") + = render 'shared/labels/sort_dropdown' .labels-container.prepend-top-5 - if @labels.any? diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index dfac62e7985..1bfd8a85f0f 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -22,6 +22,7 @@ %span.input-group-append %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } = icon("search") + = render 'shared/labels/sort_dropdown' .labels-container.prepend-top-10 - if can_admin_label diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml new file mode 100644 index 00000000000..ff6e2947ffd --- /dev/null +++ b/app/views/shared/labels/_sort_dropdown.html.haml @@ -0,0 +1,9 @@ +- sort_title = label_sort_options_hash[@sort] || sort_title_name_desc +.dropdown.inline + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } } + = sort_title + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort + %li + - label_sort_options_hash.each do |value, title| + = sortable_item(title, page_filter_path(sort: value, label: true), sort_title) diff --git a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml new file mode 100644 index 00000000000..24e231ed88a --- /dev/null +++ b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml @@ -0,0 +1,5 @@ +--- +title: Add sorting for labels on labels page +merge_request: 21642 +author: +type: added diff --git a/spec/features/groups/labels/sort_labels_spec.rb b/spec/features/groups/labels/sort_labels_spec.rb new file mode 100644 index 00000000000..2aea4d77675 --- /dev/null +++ b/spec/features/groups/labels/sort_labels_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Sort labels', :js do + let(:user) { create(:user) } + let(:group) { create(:group) } + let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) } + let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) } + + before do + group.add_maintainer(user) + sign_in(user) + + visit group_labels_path(group) + end + + it 'sorts by title by default' do + expect(page).to have_button('Name') + + # assert default sorting + within '.other-labels' do + expect(page.all('.label-list-item').first.text).to include('Bar') + expect(page.all('.label-list-item').last.text).to include('Foo') + end + end + + it 'sorts by date' do + click_button 'Name' + + sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text) + + expect(sort_options[0]).to eq('Name') + expect(sort_options[1]).to eq('Name, descending') + expect(sort_options[2]).to eq('Last created') + expect(sort_options[3]).to eq('Oldest created') + expect(sort_options[4]).to eq('Last updated') + expect(sort_options[5]).to eq('Oldest updated') + + click_link 'Name, descending' + + # assert default sorting + within '.other-labels' do + expect(page.all('.label-list-item').first.text).to include('Foo') + expect(page.all('.label-list-item').last.text).to include('Bar') + end + end +end diff --git a/spec/features/projects/labels/sort_labels_spec.rb b/spec/features/projects/labels/sort_labels_spec.rb new file mode 100644 index 00000000000..01c3f251173 --- /dev/null +++ b/spec/features/projects/labels/sort_labels_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Sort labels', :js do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:label1) { create(:label, title: 'Foo', description: 'Lorem ipsum', project: project) } + let!(:label2) { create(:label, title: 'Bar', description: 'Fusce consequat', project: project) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit project_labels_path(project) + end + + it 'sorts by title by default' do + expect(page).to have_button('Name') + + # assert default sorting + within '.other-labels' do + expect(page.all('.label-list-item').first.text).to include('Bar') + expect(page.all('.label-list-item').last.text).to include('Foo') + end + end + + it 'sorts by date' do + click_button 'Name' + + sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text) + + expect(sort_options[0]).to eq('Name') + expect(sort_options[1]).to eq('Name, descending') + expect(sort_options[2]).to eq('Last created') + expect(sort_options[3]).to eq('Oldest created') + expect(sort_options[4]).to eq('Last updated') + expect(sort_options[5]).to eq('Oldest updated') + + click_link 'Name, descending' + + # assert default sorting + within '.other-labels' do + expect(page.all('.label-list-item').first.text).to include('Foo') + expect(page.all('.label-list-item').last.text).to include('Bar') + end + end +end