From fbb4f15d1b656e23ff1700b40101cad1537b54ca Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Thu, 6 Dec 2018 16:57:19 +0000 Subject: [PATCH] Sort issues and merge requests in ascending and descending order --- app/assets/stylesheets/pages/issues.scss | 10 + .../concerns/issuable_collections.rb | 6 - app/helpers/sorting_helper.rb | 47 ++++ app/models/concerns/awardable.rb | 13 +- app/models/concerns/issuable.rb | 28 ++- app/views/shared/_sort_dropdown.html.haml | 16 -- app/views/shared/issuable/_filter.html.haml | 32 +++ .../shared/issuable/_search_bar.html.haml | 5 +- .../shared/issuable/_sort_dropdown.html.haml | 20 ++ .../unreleased/39849_controller_sorts.yml | 5 + .../issuables/default_sort_order_spec.rb | 179 -------------- spec/features/issuables/sorting_list_spec.rb | 226 ++++++++++++++++++ .../filtered_search/filter_issues_spec.rb | 2 +- .../features/issues/user_sorts_issues_spec.rb | 8 +- .../user_sorts_merge_requests_spec.rb | 12 +- .../labels/issues_sorted_by_priority_spec.rb | 4 +- spec/helpers/sorting_helper_spec.rb | 43 ++++ .../helpers/features/sorting_helpers.rb | 4 +- 18 files changed, 424 insertions(+), 236 deletions(-) delete mode 100644 app/views/shared/_sort_dropdown.html.haml create mode 100644 app/views/shared/issuable/_filter.html.haml create mode 100644 app/views/shared/issuable/_sort_dropdown.html.haml create mode 100644 changelogs/unreleased/39849_controller_sorts.yml delete mode 100644 spec/features/issuables/default_sort_order_spec.rb create mode 100644 spec/features/issuables/sorting_list_spec.rb create mode 100644 spec/helpers/sorting_helper_spec.rb diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 8ea34f5d19d..bb6b6f84849 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -259,6 +259,16 @@ ul.related-merge-requests > li { display: block; } +.issue-sort-dropdown { + .btn-group { + width: 100%; + } + + .reverse-sort-btn { + color: $gl-text-color-secondary; + } +} + @include media-breakpoint-up(sm) { .emoji-block .row { display: flex; diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 0837599977f..9aa98e2ca1f 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -167,12 +167,6 @@ module IssuableCollections case value when 'id_asc' then sort_value_oldest_created when 'id_desc' then sort_value_recently_created - when 'created_asc' then sort_value_created_date - when 'created_desc' then sort_value_created_date - when 'due_date_asc' then sort_value_due_date - when 'due_date_desc' then sort_value_due_date - when 'milestone_due_asc' then sort_value_milestone - when 'milestone_due_desc' then sort_value_milestone when 'downvotes_asc' then sort_value_popularity when 'downvotes_desc' then sort_value_popularity else value diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 74113aee89d..f51b96ba8ce 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -136,6 +136,53 @@ module SortingHelper link_to item, path, class: sorted_by == item ? 'is-active' : '' end + def issuable_sort_option_overrides + { + sort_value_oldest_created => sort_value_created_date, + sort_value_oldest_updated => sort_value_recently_updated, + sort_value_milestone_later => sort_value_milestone + } + end + + def issuable_reverse_sort_order_hash + { + sort_value_created_date => sort_value_oldest_created, + sort_value_recently_created => sort_value_oldest_created, + sort_value_recently_updated => sort_value_oldest_updated, + sort_value_milestone => sort_value_milestone_later + }.merge(issuable_sort_option_overrides) + end + + def issuable_sort_option_title(sort_value) + sort_value = issuable_sort_option_overrides[sort_value] || sort_value + + sort_options_hash[sort_value] + end + + def issuable_sort_direction_button(sort_value) + link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort' + reverse_sort = issuable_reverse_sort_order_hash[sort_value] + + if reverse_sort + reverse_url = page_filter_path(sort: reverse_sort) + else + reverse_url = '#' + link_class += ' disabled' + end + + link_to(reverse_url, type: 'button', class: link_class, title: 'Sort direction') do + icon_suffix = + case sort_value + when sort_value_milestone, sort_value_due_date, /_asc\z/ + 'lowest' + else + 'highest' + end + + sprite_icon("sort-#{icon_suffix}", size: 16) + end + end + # Titles. def sort_title_access_level_asc s_('SortOptions|Access level, ascending') diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 60b7ec2815c..14bc56f0eee 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -43,14 +43,19 @@ module Awardable end def order_upvotes_desc - order_votes_desc(AwardEmoji::UPVOTE_NAME) + order_votes(AwardEmoji::UPVOTE_NAME, 'DESC') + end + + def order_upvotes_asc + order_votes(AwardEmoji::UPVOTE_NAME, 'ASC') end def order_downvotes_desc - order_votes_desc(AwardEmoji::DOWNVOTE_NAME) + order_votes(AwardEmoji::DOWNVOTE_NAME, 'DESC') end - def order_votes_desc(emoji_name) + # Order votes by emoji, optional sort order param `descending` defaults to true + def order_votes(emoji_name, direction) awardable_table = self.arel_table awards_table = AwardEmoji.arel_table @@ -62,7 +67,7 @@ module Awardable ) ).join_sources - joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC") + joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) #{direction}") end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5080fe03cc8..0d363ec68b7 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,14 +145,16 @@ module Issuable def sort_by_attribute(method, excluded_labels: []) sorted = case method.to_s - when 'downvotes_desc' then order_downvotes_desc - when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels) - when 'milestone' then order_milestone_due_asc - when 'milestone_due_asc' then order_milestone_due_asc - when 'milestone_due_desc' then order_milestone_due_desc - when 'popularity' then order_upvotes_desc - when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels) - when 'upvotes_desc' then order_upvotes_desc + when 'downvotes_desc' then order_downvotes_desc + when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels) + when 'label_priority_desc' then order_labels_priority('DESC', excluded_labels: excluded_labels) + when 'milestone', 'milestone_due_asc' then order_milestone_due_asc + when 'milestone_due_desc' then order_milestone_due_desc + when 'popularity', 'popularity_desc' then order_upvotes_desc + when 'popularity_asc' then order_upvotes_asc + when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels) + when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels) + when 'upvotes_desc' then order_upvotes_desc else order_by(method) end @@ -160,7 +162,7 @@ module Issuable sorted.with_order_id_desc end - def order_due_date_and_labels_priority(excluded_labels: []) + def order_due_date_and_labels_priority(direction = 'ASC', excluded_labels: []) # The order_ methods also modify the query in other ways: # # - For milestones, we add a JOIN. @@ -177,11 +179,11 @@ module Issuable order_milestone_due_asc .order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date]) - .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'), - Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) + .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, direction), + Gitlab::Database.nulls_last_order('highest_priority', direction)) end - def order_labels_priority(excluded_labels: [], extra_select_columns: []) + def order_labels_priority(direction = 'ASC', excluded_labels: [], extra_select_columns: []) params = { target_type: name, target_column: "#{table_name}.id", @@ -198,7 +200,7 @@ module Issuable select(select_columns.join(', ')) .group(arel_table[:id]) - .reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) + .reorder(Gitlab::Database.nulls_last_order('highest_priority', direction)) end def with_label(title, sort = nil) diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml deleted file mode 100644 index e4463c1e0d8..00000000000 --- a/app/views/shared/_sort_dropdown.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -- sorted_by = sort_options_hash[@sort] -- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' - -.dropdown.inline.prepend-left-10 - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } } - = sorted_by - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - %li - = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sorted_by) - = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by) - = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sorted_by) - = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone, label: true), sorted_by) - = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date, label: true), sorted_by) if viewing_issues - = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity, label: true), sorted_by) - = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority, label: true), sorted_by) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml new file mode 100644 index 00000000000..2ca4657851c --- /dev/null +++ b/app/views/shared/issuable/_filter.html.haml @@ -0,0 +1,32 @@ +.issues-filters + .issues-details-filters.row-content-block.second-block + = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do + - if params[:search].present? + = hidden_field_tag :search, params[:search] + .issues-other-filters + .filter-item.inline + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", + placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) + + .filter-item.inline + - if params[:assignee_id].present? + = hidden_field_tag(:assignee_id, params[:assignee_id]) + = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) + + .filter-item.inline.milestone-filter + = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true + + .filter-item.inline.labels-filter + = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + + - unless @no_filters_set + .float-right + = render 'shared/issuable/sort_dropdown' + + - has_labels = @labels && @labels.any? + .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } + - if has_labels + = render 'shared/labels_row', labels: @labels diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 7c5af0b9775..46634693067 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -2,7 +2,6 @@ - board = local_assigns.fetch(:board, nil) - block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' - user_can_admin_list = board && can?(current_user, :admin_list, board.parent) -- show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true) .issues-filters .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } @@ -142,5 +141,5 @@ - if @project #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } } #js-toggle-focus-btn - - elsif show_sorting_dropdown - = render 'shared/sort_dropdown' + - elsif type != :boards_modal + = render 'shared/issuable/sort_dropdown' diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml new file mode 100644 index 00000000000..c211b9fcaa2 --- /dev/null +++ b/app/views/shared/issuable/_sort_dropdown.html.haml @@ -0,0 +1,20 @@ +- sort_value = @sort +- sort_title = issuable_sort_option_title(sort_value) +- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' + +.dropdown.inline.prepend-left-10.issue-sort-dropdown + .btn-group{ role: 'group' } + .btn-group{ role: 'group' } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = sort_title + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + %li + = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sort_title) + = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sort_title) + = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sort_title) + = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone, label: true), sort_title) + = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date, label: true), sort_title) if viewing_issues + = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity, label: true), sort_title) + = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority, label: true), sort_title) + = issuable_sort_direction_button(sort_value) diff --git a/changelogs/unreleased/39849_controller_sorts.yml b/changelogs/unreleased/39849_controller_sorts.yml new file mode 100644 index 00000000000..5fad0cb4ede --- /dev/null +++ b/changelogs/unreleased/39849_controller_sorts.yml @@ -0,0 +1,5 @@ +--- +title: Allow sorting issues and MRs in reverse order +merge_request: 21438 +author: +type: changed diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb deleted file mode 100644 index caee7a67aec..00000000000 --- a/spec/features/issuables/default_sort_order_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -require 'spec_helper' - -describe 'Projects > Issuables > Default sort order' do - let(:project) { create(:project, :public) } - - let(:first_created_issuable) { issuables.order_created_asc.first } - let(:last_created_issuable) { issuables.order_created_desc.first } - - let(:first_updated_issuable) { issuables.order_updated_asc.first } - let(:last_updated_issuable) { issuables.order_updated_desc.first } - - context 'for merge requests' do - include MergeRequestHelpers - - let!(:issuables) do - timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, - { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, - { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] - - timestamps.each_with_index do |ts, i| - create issuable_type, { title: "#{issuable_type}_#{i}", - source_branch: "#{issuable_type}_#{i}", - source_project: project }.merge(ts) - end - - MergeRequest.all - end - - context 'in the "merge requests" tab', :js do - let(:issuable_type) { :merge_request } - - it 'is "last created"' do - visit_merge_requests project - - expect(first_merge_request).to include(last_created_issuable.title) - expect(last_merge_request).to include(first_created_issuable.title) - end - end - - context 'in the "merge requests / open" tab', :js do - let(:issuable_type) { :merge_request } - - it 'is "created date"' do - visit_merge_requests_with_state(project, 'open') - - expect(selected_sort_order).to eq('created date') - expect(first_merge_request).to include(last_created_issuable.title) - expect(last_merge_request).to include(first_created_issuable.title) - end - end - - context 'in the "merge requests / merged" tab', :js do - let(:issuable_type) { :merged_merge_request } - - it 'is "last updated"' do - visit_merge_requests_with_state(project, 'merged') - - expect(find('.issues-other-filters')).to have_content('Last updated') - expect(first_merge_request).to include(last_updated_issuable.title) - expect(last_merge_request).to include(first_updated_issuable.title) - end - end - - context 'in the "merge requests / closed" tab', :js do - let(:issuable_type) { :closed_merge_request } - - it 'is "last updated"' do - visit_merge_requests_with_state(project, 'closed') - - expect(find('.issues-other-filters')).to have_content('Last updated') - expect(first_merge_request).to include(last_updated_issuable.title) - expect(last_merge_request).to include(first_updated_issuable.title) - end - end - - context 'in the "merge requests / all" tab', :js do - let(:issuable_type) { :merge_request } - - it 'is "created date"' do - visit_merge_requests_with_state(project, 'all') - - expect(find('.issues-other-filters')).to have_content('Created date') - expect(first_merge_request).to include(last_created_issuable.title) - expect(last_merge_request).to include(first_created_issuable.title) - end - end - end - - context 'for issues' do - include IssueHelpers - - let!(:issuables) do - timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, - { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, - { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] - - timestamps.each_with_index do |ts, i| - create issuable_type, { title: "#{issuable_type}_#{i}", - project: project }.merge(ts) - end - - Issue.all - end - - context 'in the "issues" tab', :js do - let(:issuable_type) { :issue } - - it 'is "created date"' do - visit_issues project - - expect(find('.issues-other-filters')).to have_content('Created date') - expect(first_issue).to include(last_created_issuable.title) - expect(last_issue).to include(first_created_issuable.title) - end - end - - context 'in the "issues / open" tab', :js do - let(:issuable_type) { :issue } - - it 'is "created date"' do - visit_issues_with_state(project, 'open') - - expect(find('.issues-other-filters')).to have_content('Created date') - expect(first_issue).to include(last_created_issuable.title) - expect(last_issue).to include(first_created_issuable.title) - end - end - - context 'in the "issues / closed" tab', :js do - let(:issuable_type) { :closed_issue } - - it 'is "last updated"' do - visit_issues_with_state(project, 'closed') - - expect(find('.issues-other-filters')).to have_content('Last updated') - expect(first_issue).to include(last_updated_issuable.title) - expect(last_issue).to include(first_updated_issuable.title) - end - end - - context 'in the "issues / all" tab', :js do - let(:issuable_type) { :issue } - - it 'is "created date"' do - visit_issues_with_state(project, 'all') - - expect(find('.issues-other-filters')).to have_content('Created date') - expect(first_issue).to include(last_created_issuable.title) - expect(last_issue).to include(first_created_issuable.title) - end - end - - context 'when the sort in the URL is id_desc' do - let(:issuable_type) { :issue } - - before do - visit_issues(project, sort: 'id_desc') - end - - it 'shows the sort order as created date' do - expect(find('.issues-other-filters')).to have_content('Created date') - expect(first_issue).to include(last_created_issuable.title) - expect(last_issue).to include(first_created_issuable.title) - end - end - end - - def selected_sort_order - find('.filter-dropdown-container .dropdown button').text.downcase - end - - def visit_merge_requests_with_state(project, state) - visit_merge_requests project, state: state - end - - def visit_issues_with_state(project, state) - visit_issues project, state: state - end -end diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb new file mode 100644 index 00000000000..0601dd47c03 --- /dev/null +++ b/spec/features/issuables/sorting_list_spec.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'Sort Issuable List' do + let(:project) { create(:project, :public) } + + let(:first_created_issuable) { issuables.order_created_asc.first } + let(:last_created_issuable) { issuables.order_created_desc.first } + + let(:first_updated_issuable) { issuables.order_updated_asc.first } + let(:last_updated_issuable) { issuables.order_updated_desc.first } + + context 'for merge requests' do + include MergeRequestHelpers + + let!(:issuables) do + timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, + { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, + { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] + + timestamps.each_with_index do |ts, i| + create issuable_type, { title: "#{issuable_type}_#{i}", + source_branch: "#{issuable_type}_#{i}", + source_project: project }.merge(ts) + end + + MergeRequest.all + end + + context 'default sort order' do + context 'in the "merge requests" tab', :js do + let(:issuable_type) { :merge_request } + + it 'is "last created"' do + visit_merge_requests project + + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + + context 'in the "merge requests / open" tab', :js do + let(:issuable_type) { :merge_request } + + it 'is "created date"' do + visit_merge_requests_with_state(project, 'open') + + expect(selected_sort_order).to eq('created date') + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + + context 'in the "merge requests / merged" tab', :js do + let(:issuable_type) { :merged_merge_request } + + it 'is "last updated"' do + visit_merge_requests_with_state(project, 'merged') + + expect(find('.issues-other-filters')).to have_content('Last updated') + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) + end + end + + context 'in the "merge requests / closed" tab', :js do + let(:issuable_type) { :closed_merge_request } + + it 'is "last updated"' do + visit_merge_requests_with_state(project, 'closed') + + expect(find('.issues-other-filters')).to have_content('Last updated') + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) + end + end + + context 'in the "merge requests / all" tab', :js do + let(:issuable_type) { :merge_request } + + it 'is "created date"' do + visit_merge_requests_with_state(project, 'all') + + expect(find('.issues-other-filters')).to have_content('Created date') + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + + context 'custom sorting' do + let(:issuable_type) { :merge_request } + + it 'supports sorting in asc and desc order' do + visit_merge_requests_with_state(project, 'open') + + page.within('.issues-other-filters') do + click_button('Created date') + click_link('Last updated') + end + + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) + + find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click + + expect(first_merge_request).to include(first_updated_issuable.title) + expect(last_merge_request).to include(last_updated_issuable.title) + end + end + end + end + + context 'for issues' do + include IssueHelpers + + let!(:issuables) do + timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, + { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, + { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] + + timestamps.each_with_index do |ts, i| + create issuable_type, { title: "#{issuable_type}_#{i}", + project: project }.merge(ts) + end + + Issue.all + end + + context 'default sort order' do + context 'in the "issues" tab', :js do + let(:issuable_type) { :issue } + + it 'is "created date"' do + visit_issues project + + expect(find('.issues-other-filters')).to have_content('Created date') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + + context 'in the "issues / open" tab', :js do + let(:issuable_type) { :issue } + + it 'is "created date"' do + visit_issues_with_state(project, 'open') + + expect(find('.issues-other-filters')).to have_content('Created date') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + + context 'in the "issues / closed" tab', :js do + let(:issuable_type) { :closed_issue } + + it 'is "last updated"' do + visit_issues_with_state(project, 'closed') + + expect(find('.issues-other-filters')).to have_content('Last updated') + expect(first_issue).to include(last_updated_issuable.title) + expect(last_issue).to include(first_updated_issuable.title) + end + end + + context 'in the "issues / all" tab', :js do + let(:issuable_type) { :issue } + + it 'is "created date"' do + visit_issues_with_state(project, 'all') + + expect(find('.issues-other-filters')).to have_content('Created date') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + + context 'when the sort in the URL is id_desc' do + let(:issuable_type) { :issue } + + before do + visit_issues(project, sort: 'id_desc') + end + + it 'shows the sort order as created date' do + expect(find('.issues-other-filters')).to have_content('Created date') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + end + + context 'custom sorting' do + let(:issuable_type) { :issue } + + it 'supports sorting in asc and desc order' do + visit_issues_with_state(project, 'open') + + page.within('.issues-other-filters') do + click_button('Created date') + click_link('Last updated') + end + + expect(first_issue).to include(last_updated_issuable.title) + expect(last_issue).to include(first_updated_issuable.title) + + find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click + + expect(first_issue).to include(first_updated_issuable.title) + expect(last_issue).to include(last_updated_issuable.title) + end + end + end + + def selected_sort_order + find('.filter-dropdown-container .dropdown button').text.downcase + end + + def visit_merge_requests_with_state(project, state) + visit_merge_requests project, state: state + end + + def visit_issues_with_state(project, state) + visit_issues project, state: state + end +end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 4d9b8262f21..a29380a180e 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -430,7 +430,7 @@ describe 'Filter issues', :js do expect_issues_list_count(2) - sort_toggle = find('.filter-dropdown-container .dropdown-menu-toggle') + sort_toggle = find('.filter-dropdown-container .dropdown') sort_toggle.click find('.filter-dropdown-container .dropdown-menu li a', text: 'Created date').click diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index 3bc93933183..eebd2d57cca 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -20,9 +20,9 @@ describe "User sorts issues" do end it 'keeps the sort option' do - find('.filter-dropdown-container button.dropdown-menu-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link('Milestone') end @@ -40,9 +40,9 @@ describe "User sorts issues" do end it "sorts by popularity" do - find(".filter-dropdown-container button.dropdown-menu-toggle").click + find('.filter-dropdown-container .dropdown').click - page.within(".content ul.dropdown-menu.dropdown-menu-right li") do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link("Popularity") end diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb index 61e8f1c4662..fa887110c13 100644 --- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb @@ -19,9 +19,9 @@ describe 'User sorts merge requests' do end it 'keeps the sort option' do - find('.filter-dropdown-container button.dropdown-menu-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link('Milestone') end @@ -49,9 +49,9 @@ describe 'User sorts merge requests' do it 'separates remember sorting with issues' do create(:issue, project: project) - find('.filter-dropdown-container button.dropdown-menu-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link('Milestone') end @@ -70,9 +70,9 @@ describe 'User sorts merge requests' do end it 'sorts by popularity' do - find('.filter-dropdown-container button.dropdown-menu-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link('Popularity') end diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index b778c72bc76..25417cf4955 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -32,7 +32,7 @@ describe 'Issue prioritization' do visit project_issues_path(project, sort: 'label_priority') # Ensure we are indicating that issues are sorted by priority - expect(page).to have_selector('.dropdown-menu-toggle', text: 'Label priority') + expect(page).to have_selector('.dropdown', text: 'Label priority') page.within('.issues-holder') do issue_titles = all('.issues-list .issue-title-text').map(&:text) @@ -70,7 +70,7 @@ describe 'Issue prioritization' do sign_in user visit project_issues_path(project, sort: 'label_priority') - expect(page).to have_selector('.dropdown-menu-toggle', text: 'Label priority') + expect(page).to have_selector('.dropdown', text: 'Label priority') page.within('.issues-holder') do issue_titles = all('.issues-list .issue-title-text').map(&:text) diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb new file mode 100644 index 00000000000..cba0d93e144 --- /dev/null +++ b/spec/helpers/sorting_helper_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe SortingHelper do + include ApplicationHelper + include IconsHelper + + describe '#issuable_sort_option_title' do + it 'returns correct title for issuable_sort_option_overrides key' do + expect(issuable_sort_option_title('created_asc')).to eq('Created date') + end + + it 'returns correct title for a valid sort value' do + expect(issuable_sort_option_title('priority')).to eq('Priority') + end + + it 'returns nil for invalid sort value' do + expect(issuable_sort_option_title('invalid_key')).to eq(nil) + end + end + + describe '#issuable_sort_direction_button' do + before do + allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: {})) + end + + it 'returns icon with sort-highest when sort is created_date' do + expect(issuable_sort_direction_button('created_date')).to include('sort-highest') + end + + it 'returns icon with sort-lowest when sort is asc' do + expect(issuable_sort_direction_button('created_asc')).to include('sort-lowest') + end + + it 'returns icon with sort-lowest when sorting by milestone' do + expect(issuable_sort_direction_button('milestone')).to include('sort-lowest') + end + + it 'returns icon with sort-lowest when sorting by due_date' do + expect(issuable_sort_direction_button('due_date')).to include('sort-lowest') + end + end +end diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index a1ae428586e..003ecb251fe 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -13,9 +13,9 @@ module Spec module Features module SortingHelpers def sort_by(value) - find('.filter-dropdown-container button.dropdown-menu-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link(value) end end