Commit 92a49f30 authored by Andreas Brandl's avatar Andreas Brandl

Merge branch '7210-add-sort-direction-button-sort-dropdown' into 'master'

Add sort direction button with sort dropdown for Epics and Roadmap

Closes #7261 and #7210

See merge request gitlab-org/gitlab-ee!7884
parents 66fff1a9 b959d7d0
......@@ -2834,6 +2834,7 @@ ActiveRecord::Schema.define(version: 20181204135932) do
t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "epics_sort"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
end
......
......@@ -121,7 +121,9 @@ You can also sort epics list by:
- **Created date**
- **Last updated**
- **Start date**
- **Due date**
- **Due date**
Each option contains a button that can toggle the order between **ascending** and **descending**. The sort option and order will be persisted to be used wherever epics are browsed including the [roadmap](../roadmap/index.md).
![epics sort](img/epics_sort.png)
......
......@@ -13,6 +13,8 @@ Epics in the view can be sorted by:
- **Start date**
- **Due date**
Each option contains a button that can toggle the order between **ascending** and **descending**. The sort option and order will be persisted to be used wherever epics are browsed including the [epics list view](../epics/index.md).
![roadmap view](img/roadmap_view.png)
## Timeline duration
......
......@@ -4,9 +4,21 @@ $details-cell-width: 320px;
$border-style: 1px solid $border-gray-normal;
$roadmap-gradient-dark-gray: rgba(0, 0, 0, 0.15);
$roadmap-gradient-gray: rgba(255, 255, 255, 0.001);
$scroll-top-gradient: linear-gradient(to bottom, $roadmap-gradient-dark-gray 0%, $roadmap-gradient-gray 100%);
$scroll-bottom-gradient: linear-gradient(to bottom, $roadmap-gradient-gray 0%, $roadmap-gradient-dark-gray 100%);
$column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%, $roadmap-gradient-gray 100%);
$scroll-top-gradient: linear-gradient(
to bottom,
$roadmap-gradient-dark-gray 0%,
$roadmap-gradient-gray 100%
);
$scroll-bottom-gradient: linear-gradient(
to bottom,
$roadmap-gradient-gray 0%,
$roadmap-gradient-dark-gray 100%
);
$column-right-gradient: linear-gradient(
to right,
$roadmap-gradient-dark-gray 0%,
$roadmap-gradient-gray 100%
);
@mixin roadmap-scroll-mixin {
height: $grid-size;
......@@ -14,6 +26,39 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
pointer-events: none;
}
.epics-details-filters {
.btn-group {
.dropdown-toggle {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.btn-sort-direction {
border-left: 0;
&:hover {
border-color: $gray-darkest;
}
}
@include media-breakpoint-down(xs) {
display: flex;
.dropdown-menu-sort {
// This is a hack to fix dropdown alignment in small screens
// where Bootstrap applies inline `transform: translate3d(...)`
// and since our dropdown button has sort direction button
// present, alignment needs to compensate for that space
// without which it appears shifted towards left.
//
// One more approach is to override `transform` using `!important`
// but that too involves using magic number
margin-left: 27px;
}
}
}
}
.epics-roadmap-filters {
.epics-details-filters {
.btn-roadmap-preset {
......@@ -52,7 +97,7 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
.roadmap-timeline-section .timeline-header-blank::after,
.epics-list-section .epic-details-cell::after {
content: '';
content: "";
position: absolute;
top: 0;
right: -$grid-size;
......@@ -136,7 +181,7 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
}
.today-bar::before {
content: '';
content: "";
position: absolute;
top: -2px;
left: -3px;
......@@ -150,7 +195,7 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
&.scroll-top-shadow .timeline-header-blank::before {
@include roadmap-scroll-mixin;
content: '';
content: "";
position: absolute;
left: 0;
bottom: -$grid-size;
......@@ -256,7 +301,7 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
&.start-date-outside::before,
&.end-date-outside::after {
content: '';
content: "";
position: absolute;
top: 0;
height: 100%;
......@@ -269,11 +314,21 @@ $column-right-gradient: linear-gradient(to right, $roadmap-gradient-dark-gray 0%
}
&.start-date-undefined {
background: linear-gradient(to right, $roadmap-gradient-gray 0%, $blue-200 50%, $blue-500 100%);
background: linear-gradient(
to right,
$roadmap-gradient-gray 0%,
$blue-200 50%,
$blue-500 100%
);
}
&.end-date-undefined {
background: linear-gradient(to right, $blue-500 0%, $blue-200 50%, $roadmap-gradient-gray 100%);
background: linear-gradient(
to right,
$blue-500 0%,
$blue-200 50%,
$roadmap-gradient-gray 100%
);
}
&.start-date-outside {
......
......@@ -11,11 +11,13 @@ module EpicsActions
end
def default_sort_order
sort_value_end_date
sort_value_recently_created
end
def update_cookie_value(value)
case value
when 'created_asc' then sort_value_oldest_created
when 'created_desc' then sort_value_recently_created
when 'start_date_asc' then sort_value_start_date
when 'end_date_asc' then sort_value_end_date
else
......
......@@ -95,6 +95,10 @@ class Groups::EpicsController < Groups::ApplicationController
EpicsFinder
end
def issuable_sorting_field
:epics_sort
end
def preload_for_collection
@preload_for_collection ||= [:group, :author]
end
......
......@@ -9,14 +9,20 @@ module Groups
before_action :group
before_action :persist_roadmap_layout, only: [:show]
# show roadmap for a group
def show
# show roadmap for a group
@sort = set_sort_order_from_cookie || default_sort_order
# Used to persist the order and show the correct sorting dropdown on UI.
@sort = set_sort_order
@epics_count = EpicsFinder.new(current_user, group_id: @group.id).execute.count
end
private
def issuable_sorting_field
:epics_sort
end
def persist_roadmap_layout
return unless current_user
......
......@@ -14,6 +14,44 @@ module EE
}.merge(super)
end
def epics_sort_options_hash
{
sort_value_created_date => sort_title_created_date,
sort_value_oldest_created => sort_title_created_date,
sort_value_recently_created => sort_title_created_date,
sort_value_oldest_updated => sort_title_recently_updated,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_start_date_later => sort_title_start_date,
sort_value_start_date_soon => sort_title_start_date,
sort_value_end_date_later => sort_title_end_date,
sort_value_end_date => sort_title_end_date
}
end
# This method is used to find the opposite ordering parameter for the sort button in the UI.
# Hash key is the descending sorting order and the sort value is the opposite of it for the same field.
# For example: created_at_asc => created_at_desc
def epics_ordering_options_hash
{
sort_value_oldest_created => sort_value_recently_created,
sort_value_oldest_updated => sort_value_recently_updated,
sort_value_start_date_soon => sort_value_start_date_later,
sort_value_end_date => sort_value_end_date_later
}
end
# Creates a button with the opposite ordering for the current field in UI.
def sort_order_button(sort)
opposite_sorting_param = epics_ordering_options_hash[sort] || epics_ordering_options_hash.key(sort)
sort_icon = sort.end_with?('desc') ? 'sort-highest' : 'sort-lowest'
link_to sprite_icon(sort_icon, size: 16),
page_filter_path(sort: opposite_sorting_param, label: true),
class: "btn btn-default has-tooltip qa-reverse-sort btn-sort-direction",
title: _("Sort direction")
end
def sort_title_start_date
s_('SortOptions|Start date')
end
......@@ -42,6 +80,10 @@ module EE
'end_date_asc'
end
def sort_value_end_date_later
'end_date_desc'
end
def sort_value_less_weight
'weight_asc'
end
......
......@@ -56,6 +56,14 @@ module EE
reorder(::Gitlab::Database.nulls_last_order('end_date'), 'id DESC')
end
scope :order_end_date_desc, -> do
reorder(::Gitlab::Database.nulls_last_order('end_date', 'DESC'), 'id DESC')
end
scope :order_start_date_desc, -> do
reorder(::Gitlab::Database.nulls_last_order('start_date', 'DESC'), 'id DESC')
end
def etag_caching_enabled?
true
end
......@@ -104,7 +112,9 @@ module EE
case method.to_s
when 'start_or_end_date' then order_start_or_end_date_asc
when 'start_date_asc' then order_start_date_asc
when 'start_date_desc' then order_start_date_desc
when 'end_date_asc' then order_end_date_asc
when 'end_date_desc' then order_end_date_desc
else
super
end
......
- sorted_by = sort_options_hash[@sort]
- sorted_by = epics_sort_options_hash[@sort]
.dropdown.inline.prepend-left-10
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
= sorted_by
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= 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_start_date, page_filter_path(sort: sort_value_start_date, label: true), sorted_by)
= sortable_item(sort_title_end_date, page_filter_path(sort: sort_value_end_date, label: true), sorted_by)
.btn-group
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
= sorted_by
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_recently_created, 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_start_date, page_filter_path(sort: sort_value_start_date_soon, label: true), sorted_by)
= sortable_item(sort_title_end_date, page_filter_path(sort: sort_value_end_date, label: true), sorted_by)
= sort_order_button(@sort)
---
title: Add sort direction button with sort dropdown for Epics and Roadmap
merge_request:
author:
type: changed
class AddEpicsSortToUserPreference < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :user_preferences, :epics_sort, :string
end
end
......@@ -57,11 +57,38 @@ describe Groups::EpicsController do
expect(response).to have_gitlab_http_status(200)
end
it 'stores sorting param in a cookie' do
get :index, group_id: group, sort: 'start_date_asc'
context 'when there is no logged in user' do
it 'stores sorting param in a cookie' do
group.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
sign_out(user)
expect(cookies['epic_sort']).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
get :index, group_id: group, sort: 'start_date_asc'
expect(cookies['epic_sort']).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
end
end
context 'when there is a logged in user' do
context 'when epics_sort is nil' do
it 'stores sorting param in user preferences' do
get :index, group_id: group, sort: 'start_date_asc'
expect(user.user_preference.epics_sort).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
end
end
context 'when epics_sort is present' do
it 'update epics_sort with current value' do
user.user_preference.update(epics_sort: 'created_desc')
get :index, group_id: group, sort: 'start_date_asc'
expect(user.reload.user_preference.epics_sort).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
end
end
end
context 'with page param' do
......
......@@ -31,11 +31,38 @@ describe Groups::RoadmapController do
expect(response).to have_gitlab_http_status(200)
end
it 'stores sorting param in a cookie' do
get :show, group_id: group, sort: 'start_date_asc'
context 'when there is no logged user' do
it 'stores epics sorting param in a cookie' do
group.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
sign_out(user)
expect(cookies['epic_sort']).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
get :show, group_id: group, sort: 'start_date_asc'
expect(cookies['epic_sort']).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
end
end
context 'when there is a user logged in' do
context 'when epics_sort is nil' do
it 'stores epics sorting param in user preference' do
get :show, group_id: group, sort: 'start_date_asc'
expect(response).to have_gitlab_http_status(200)
expect(user.reload.user_preference.epics_sort).to eq('start_date_asc')
end
end
context 'when epics_sort is present' do
it 'update epics_sort with current value' do
user.user_preference.update(epics_sort: 'created_desc')
get :show, group_id: group, sort: 'start_date_asc'
expect(user.reload.user_preference.epics_sort).to eq('start_date_asc')
expect(response).to have_gitlab_http_status(200)
end
end
end
end
end
......
......@@ -43,23 +43,23 @@ describe 'epics list', :js do
end
end
it 'sorts by end_date ASC by default' do
expect(page).to have_button('Due date')
it 'sorts by created_at DESC by default' do
expect(page).to have_button('Created date')
page.within('.content-wrapper .content') do
expect(find('.top-area')).to have_content('All 3')
page.within(".issuable-list") do
page.within("li:nth-child(1)") do
expect(page).to have_content(epic1.title)
expect(page).to have_content(epic3.title)
end
page.within("li:nth-child(2)") do
expect(page).to have_content(epic3.title)
expect(page).to have_content(epic2.title)
end
page.within("li:nth-child(3)") do
expect(page).to have_content(epic2.title)
expect(page).to have_content(epic1.title)
end
end
end
......@@ -67,7 +67,7 @@ describe 'epics list', :js do
it 'sorts by the selected value and stores the selection for epic list & roadmap' do
page.within('.epics-other-filters') do
click_button 'Due date'
click_button 'Created date'
sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
expect(sort_options[0]).to eq('Created date')
......
......@@ -48,10 +48,18 @@ describe Epic do
expect(epics(:start_date_asc)).to eq([epic1, epic2, epic4, epic3])
end
it 'orders by start_date DESC' do
expect(epics(:start_date_desc)).to eq([epic2, epic1, epic4, epic3])
end
it 'orders by end_date ASC' do
expect(epics(:end_date_asc)).to eq([epic3, epic1, epic4, epic2])
end
it 'orders by end_date DESC' do
expect(epics(:end_date_desc)).to eq([epic1, epic3, epic4, epic2])
end
it 'orders by updated_at ASC' do
expect(epics(:updated_asc)).to eq([epic2, epic3, epic1, epic4])
end
......
......@@ -7885,6 +7885,9 @@ msgstr ""
msgid "Sort by"
msgstr ""
msgid "Sort direction"
msgstr ""
msgid "SortOptions|Access level, ascending"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment