Commit f050a12e authored by Douwe Maan's avatar Douwe Maan

Merge branch 'id-51433-sort-wiki-by-date' into 'master'

Allow to sort wiki pages by date and title

See merge request gitlab-org/gitlab-ce!25365
parents 8647230f 866b58a5
...@@ -16,7 +16,10 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -16,7 +16,10 @@ class Projects::WikisController < Projects::ApplicationController
end end
def pages def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]) @wiki_pages = Kaminari.paginate_array(
@project_wiki.pages(sort: params[:sort], direction: params[:direction])
).page(params[:page])
@wiki_entries = WikiPage.group_by_directory(@wiki_pages) @wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end end
......
...@@ -47,4 +47,24 @@ module WikiHelper ...@@ -47,4 +47,24 @@ module WikiHelper
def wiki_attachment_upload_url def wiki_attachment_upload_url
expose_url(api_v4_projects_wikis_attachments_path(id: @project.id)) expose_url(api_v4_projects_wikis_attachments_path(id: @project.id))
end end
def wiki_sort_controls(project, sort, direction)
sort ||= ProjectWiki::TITLE_ORDER
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
icon_class = direction == 'desc' ? 'highest' : 'lowest'
link_to(project_wikis_pages_path(project, sort: sort, direction: reversed_direction),
type: 'button', class: link_class, title: _('Sort direction')) do
sprite_icon("sort-#{icon_class}", size: 16)
end
end
def wiki_sort_title(key)
if key == ProjectWiki::CREATED_AT_ORDER
s_("Wiki|Created date")
else
s_("Wiki|Title")
end
end
end end
...@@ -13,6 +13,11 @@ class ProjectWiki ...@@ -13,6 +13,11 @@ class ProjectWiki
CouldNotCreateWikiError = Class.new(StandardError) CouldNotCreateWikiError = Class.new(StandardError)
SIDEBAR = '_sidebar' SIDEBAR = '_sidebar'
TITLE_ORDER = 'title'
CREATED_AT_ORDER = 'created_at'
DIRECTION_DESC = 'desc'
DIRECTION_ASC = 'asc'
# Returns a string describing what went wrong after # Returns a string describing what went wrong after
# an operation fails. # an operation fails.
attr_reader :error_message attr_reader :error_message
...@@ -82,8 +87,15 @@ class ProjectWiki ...@@ -82,8 +87,15 @@ class ProjectWiki
# Returns an Array of GitLab WikiPage instances or an # Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages. # empty Array if this Wiki has no pages.
def pages(limit: 0) def pages(limit: 0, sort: nil, direction: DIRECTION_ASC)
wiki.pages(limit: limit).map { |page| WikiPage.new(self, page, true) } sort ||= TITLE_ORDER
direction_desc = direction == DIRECTION_DESC
wiki.pages(
limit: limit, sort: sort, direction_desc: direction_desc
).map do |page|
WikiPage.new(self, page, true)
end
end end
# Finds a page within the repository based on a tile # Finds a page within the repository based on a tile
......
...@@ -28,16 +28,15 @@ class WikiPage ...@@ -28,16 +28,15 @@ class WikiPage
def self.group_by_directory(pages) def self.group_by_directory(pages)
return [] if pages.blank? return [] if pages.blank?
pages.sort_by { |page| [page.directory, page.slug] } pages.each_with_object([]) do |page, grouped_pages|
.group_by(&:directory) next grouped_pages << page unless page.directory.present?
.map do |dir, pages|
if dir.present? directory = grouped_pages.find { |dir| dir.slug == page.directory }
WikiDirectory.new(dir, pages)
else next directory.pages << page if directory
pages
end grouped_pages << WikiDirectory.new(page.directory, [page])
end end
.flatten
end end
def self.unhyphenize(name) def self.unhyphenize(name)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) - add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title s_("Wiki|Pages") - breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki") - page_title s_("Wiki|Pages"), _("Wiki")
- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class } %div{ class: container_class }
.wiki-page-header .wiki-page-header
...@@ -15,6 +16,18 @@ ...@@ -15,6 +16,18 @@
= icon('cloud-download') = icon('cloud-download')
= _("Clone repository") = _("Clone repository")
.dropdown.inline.wiki-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(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title)
= sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title)
= wiki_sort_controls(@project, params[:sort], params[:direction])
%ul.wiki-pages-list.content-list %ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages' = render @wiki_entries, context: 'pages'
......
---
title: Allow to sort wiki pages by date and title
merge_request: 25365
author:
type: added
...@@ -86,9 +86,9 @@ module Gitlab ...@@ -86,9 +86,9 @@ module Gitlab
end end
end end
def pages(limit: 0) def pages(limit: 0, sort: nil, direction_desc: false)
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_get_all_pages(limit: limit) gitaly_get_all_pages(limit: limit, sort: sort, direction_desc: direction_desc)
end end
end end
...@@ -168,8 +168,10 @@ module Gitlab ...@@ -168,8 +168,10 @@ module Gitlab
Gitlab::Git::WikiFile.new(wiki_file) Gitlab::Git::WikiFile.new(wiki_file)
end end
def gitaly_get_all_pages(limit: 0) def gitaly_get_all_pages(limit: 0, sort: nil, direction_desc: false)
gitaly_wiki_client.get_all_pages(limit: limit).map do |wiki_page, version| gitaly_wiki_client.get_all_pages(
limit: limit, sort: sort, direction_desc: direction_desc
).map do |wiki_page, version|
Gitlab::Git::WikiPage.new(wiki_page, version) Gitlab::Git::WikiPage.new(wiki_page, version)
end end
end end
......
...@@ -87,8 +87,13 @@ module Gitlab ...@@ -87,8 +87,13 @@ module Gitlab
wiki_page_from_iterator(response) wiki_page_from_iterator(response)
end end
def get_all_pages(limit: 0) def get_all_pages(limit: 0, sort: nil, direction_desc: false)
request = Gitaly::WikiGetAllPagesRequest.new(repository: @gitaly_repo, limit: limit) sort_value = Gitaly::WikiGetAllPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
params[:sort] = sort_value if sort_value
request = Gitaly::WikiGetAllPagesRequest.new(params)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout)
pages = [] pages = []
......
...@@ -7417,6 +7417,9 @@ msgstr "" ...@@ -7417,6 +7417,9 @@ msgstr ""
msgid "Sort by" msgid "Sort by"
msgstr "" msgstr ""
msgid "Sort direction"
msgstr ""
msgid "SortOptions|Access level, ascending" msgid "SortOptions|Access level, ascending"
msgstr "" msgstr ""
...@@ -9197,6 +9200,9 @@ msgstr "" ...@@ -9197,6 +9200,9 @@ msgstr ""
msgid "Wiki|Create page" msgid "Wiki|Create page"
msgstr "" msgstr ""
msgid "Wiki|Created date"
msgstr ""
msgid "Wiki|Edit Page" msgid "Wiki|Edit Page"
msgstr "" msgstr ""
...@@ -9215,6 +9221,9 @@ msgstr "" ...@@ -9215,6 +9221,9 @@ msgstr ""
msgid "Wiki|Pages" msgid "Wiki|Pages"
msgstr "" msgstr ""
msgid "Wiki|Title"
msgstr ""
msgid "Wiki|Wiki Pages" msgid "Wiki|Wiki Pages"
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe 'User views wiki pages' do
include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let!(:wiki_page1) do
create(:wiki_page, wiki: project.wiki, attrs: { title: '3 home', content: '3' })
end
let!(:wiki_page2) do
create(:wiki_page, wiki: project.wiki, attrs: { title: '1 home', content: '1' })
end
let!(:wiki_page3) do
create(:wiki_page, wiki: project.wiki, attrs: { title: '2 home', content: '2' })
end
let(:pages) do
page.find('.wiki-pages-list').all('li').map { |li| li.find('a') }
end
before do
project.add_maintainer(user)
sign_in(user)
visit(project_wikis_pages_path(project))
end
context 'ordered by title' do
let(:pages_ordered_by_title) { [wiki_page2, wiki_page3, wiki_page1] }
context 'asc' do
it 'pages are displayed in direct order' do
pages.each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_title[index].title)
end
end
end
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
page.find('.qa-reverse-sort').click
end
end
it 'pages are displayed in reversed order' do
pages.reverse_each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_title[index].title)
end
end
end
end
context 'ordered by created_at' do
let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] }
before do
page.within('.wiki-sort-dropdown') do
click_button('Title')
click_link('Created date')
end
end
context 'asc' do
it 'pages are displayed in direct order' do
pages.each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
page.find('.qa-reverse-sort').click
end
end
it 'pages are displayed in reversed order' do
pages.reverse_each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
end
end
...@@ -18,4 +18,56 @@ describe WikiHelper do ...@@ -18,4 +18,56 @@ describe WikiHelper do
end end
end end
end end
describe '#wiki_sort_controls' do
let(:project) { create(:project) }
let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) }
let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" }
def expected_link(sort, direction, icon_class)
path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
helper.link_to(path, type: 'button', class: classes, title: 'Sort direction') do
helper.sprite_icon("sort-#{icon_class}", size: 16)
end
end
context 'initial call' do
let(:sort) { nil }
let(:direction) { nil }
it 'renders with default values' do
expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest'))
end
end
context 'sort by title' do
let(:sort) { 'title' }
let(:direction) { 'asc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest'))
end
end
context 'sort by created_at' do
let(:sort) { 'created_at' }
let(:direction) { 'desc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('created_at', 'asc', 'highest'))
end
end
end
describe '#wiki_sort_title' do
it 'returns a title corresponding to a key' do
expect(helper.wiki_sort_title('created_at')).to eq('Created date')
expect(helper.wiki_sort_title('title')).to eq('Title')
end
it 'defaults to Title if a key is unknown' do
expect(helper.wiki_sort_title('unknown')).to eq('Title')
end
end
end end
...@@ -20,12 +20,16 @@ describe WikiPage do ...@@ -20,12 +20,16 @@ describe WikiPage do
context 'when there are pages' do context 'when there are pages' do
before do before do
create_page('dir_1/dir_1_1/page_3', 'content') create_page('dir_1/dir_1_1/page_3', 'content')
create_page('page_1', 'content')
create_page('dir_1/page_2', 'content') create_page('dir_1/page_2', 'content')
create_page('dir_2/page_5', 'content') create_page('dir_2/page_5', 'content')
create_page('page_6', 'content')
create_page('dir_2/page_4', 'content') create_page('dir_2/page_4', 'content')
create_page('page_1', 'content')
end end
let(:page_1) { wiki.find_page('page_1') } let(:page_1) { wiki.find_page('page_1') }
let(:page_6) { wiki.find_page('page_6') }
let(:dir_1) do let(:dir_1) do
WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')]) WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
end end
...@@ -38,11 +42,26 @@ describe WikiPage do ...@@ -38,11 +42,26 @@ describe WikiPage do
WikiDirectory.new('dir_2', pages) WikiDirectory.new('dir_2', pages)
end end
context 'sort by title' do
let(:grouped_entries) { described_class.group_by_directory(wiki.pages) }
let(:expected_grouped_entries) { [dir_1_1, dir_1, dir_2, page_1, page_6] }
it 'returns an array with pages and directories' do it 'returns an array with pages and directories' do
expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2] grouped_entries.each_with_index do |page_or_dir, i|
expected_page_or_dir = expected_grouped_entries[i]
expected_slugs = get_slugs(expected_page_or_dir)
slugs = get_slugs(page_or_dir)
grouped_entries = described_class.group_by_directory(wiki.pages) expect(slugs).to match_array(expected_slugs)
end
end
end
context 'sort by created_at' do
let(:grouped_entries) { described_class.group_by_directory(wiki.pages(sort: 'created_at')) }
let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, dir_2, page_6] }
it 'returns an array with pages and directories' do
grouped_entries.each_with_index do |page_or_dir, i| grouped_entries.each_with_index do |page_or_dir, i|
expected_page_or_dir = expected_grouped_entries[i] expected_page_or_dir = expected_grouped_entries[i]
expected_slugs = get_slugs(expected_page_or_dir) expected_slugs = get_slugs(expected_page_or_dir)
...@@ -51,12 +70,10 @@ describe WikiPage do ...@@ -51,12 +70,10 @@ describe WikiPage do
expect(slugs).to match_array(expected_slugs) expect(slugs).to match_array(expected_slugs)
end end
end end
end
it 'returns an array sorted by alphabetical position' do it 'returns an array with retained order with directories at the top' do
# Directories and pages within directories are sorted alphabetically. expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
# Pages at root come before everything.
expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
'dir_2/page_4', 'dir_2/page_5']
grouped_entries = described_class.group_by_directory(wiki.pages) grouped_entries = described_class.group_by_directory(wiki.pages)
......
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