Commit a1c7d27d authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'ee-sort-wiki-pages-by-date' into 'master'

Sort wiki pages by date

See merge request gitlab-org/gitlab!16172
parents f72dcdd3 d8fafdf5
...@@ -343,8 +343,6 @@ linters: ...@@ -343,8 +343,6 @@ linters:
- 'app/views/projects/triggers/_index.html.haml' - 'app/views/projects/triggers/_index.html.haml'
- 'app/views/projects/triggers/_trigger.html.haml' - 'app/views/projects/triggers/_trigger.html.haml'
- 'app/views/projects/triggers/edit.html.haml' - 'app/views/projects/triggers/edit.html.haml'
- 'app/views/projects/wikis/_new.html.haml'
- 'app/views/projects/wikis/_pages_wiki_page.html.haml'
- 'app/views/projects/wikis/edit.html.haml' - 'app/views/projects/wikis/edit.html.haml'
- 'app/views/projects/wikis/history.html.haml' - 'app/views/projects/wikis/history.html.haml'
- 'app/views/repository_check_mailer/notify.html.haml' - 'app/views/repository_check_mailer/notify.html.haml'
......
// currently, this controller inherits all behaviors from wikis
import '../wikis/index';
import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import csrf from '~/lib/utils/csrf';
import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki';
import ZenMode from '~/zen_mode';
import GLForm from '~/gl_form';
import deleteWikiModal from '../wikis/components/delete_wiki_modal.vue';
import Wikis from '../wikis/wikis';
document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form')); // eslint-disable-line no-new
const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper');
if (deleteWikiModalWrapperEl) {
Vue.use(Translate);
const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset;
// eslint-disable-next-line no-new
new Vue({
el: deleteWikiModalWrapperEl,
data: {
deleteWikiUrl: '',
},
render(createElement) {
return createElement(deleteWikiModal, {
props: {
pageTitle,
deleteWikiUrl,
csrfToken: csrf.token,
},
});
},
});
}
});
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import csrf from '~/lib/utils/csrf';
import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki';
import GLForm from '~/gl_form';
import Wikis from './wikis'; import Wikis from './wikis';
import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form';
import deleteWikiModal from './components/delete_wiki_modal.vue';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form')); // eslint-disable-line no-new new GLForm($('.wiki-form')); // eslint-disable-line no-new
const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper');
if (deleteWikiModalWrapperEl) {
Vue.use(Translate);
const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset;
// eslint-disable-next-line no-new
new Vue({
el: deleteWikiModalWrapperEl,
data: {
deleteWikiUrl: '',
},
render(createElement) {
return createElement(deleteWikiModal, {
props: {
pageTitle,
deleteWikiUrl,
csrfToken: csrf.token,
},
});
},
});
}
}); });
...@@ -12,8 +12,8 @@ export default class Wikis { ...@@ -12,8 +12,8 @@ export default class Wikis {
} }
this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page')); this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page'));
this.editTitleInput = document.querySelector('form.wiki-form #wiki_title'); this.editTitleInput = document.querySelector('form.wiki-form #wiki_page_title');
this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message'); this.commitMessageInput = document.querySelector('form.wiki-form #wiki_page_message');
this.commitMessageI18n = this.isNewWikiPage this.commitMessageI18n = this.isNewWikiPage
? s__('WikiPageCreate|Create %{pageTitle}') ? s__('WikiPageCreate|Create %{pageTitle}')
: s__('WikiPageEdit|Update %{pageTitle}'); : s__('WikiPageEdit|Update %{pageTitle}');
......
...@@ -99,3 +99,18 @@ ...@@ -99,3 +99,18 @@
justify-content: center; justify-content: center;
color: $gray-700; color: $gray-700;
} }
.svg-icon {
display: inline-flex;
align-self: center;
svg {
height: 1em;
width: 1em;
}
&.svg-baseline svg {
top: 0.125em;
position: relative;
}
}
.new-wiki-page {
.new-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.wiki-history,
.wiki-page,
.edit-wiki-page {
margin-right: $gutter-width;
}
.edit-wiki-page {
@media only screen and (min-width: map-get($grid-breakpoints, lg) + (2 * $gutter-width)) and (max-width: map-get($grid-breakpoints, lg) + (3 * $gutter-width)) {
margin-right: $gutter-width * 1.5;
}
}
.wiki-form {
.edit-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.title .edit-wiki-header { .title .edit-wiki-header {
width: 780px; width: 780px;
margin-left: auto; margin-left: auto;
...@@ -5,6 +33,15 @@ ...@@ -5,6 +33,15 @@
padding-right: 7px; padding-right: 7px;
} }
.container-fluid.wiki-page,
.container-fluid.edit-wiki-page {
width: initial;
}
.wiki-history.breadcrumbs {
min-height: (2 * $gl-padding) + 22;
}
.wiki-page-header { .wiki-page-header {
position: relative; position: relative;
...@@ -79,7 +116,7 @@ ...@@ -79,7 +116,7 @@
} }
.sidebar-container { .sidebar-container {
padding: $gl-padding 0; padding-bottom: $gl-padding;
width: calc(100% + 100px); width: calc(100% + 100px);
padding-right: 100px; padding-right: 100px;
height: 100%; height: 100%;
...@@ -125,7 +162,7 @@ ...@@ -125,7 +162,7 @@
} }
.wiki-sidebar-header { .wiki-sidebar-header {
padding: 0 $gl-padding $gl-padding; padding: $gl-padding;
.gutter-toggle { .gutter-toggle {
margin-top: 0; margin-top: 0;
......
...@@ -9,11 +9,10 @@ module PreviewMarkdown ...@@ -9,11 +9,10 @@ module PreviewMarkdown
markdown_params = markdown_params =
case controller_name case controller_name
when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true } when 'snippets' then { skip_project_check: true }
when 'groups' then { group: group } when 'groups' then { group: group }
when 'projects' then projects_filter_params when 'projects' then projects_filter_params
else {} else preview_markdown_params
end end
render json: { render json: {
...@@ -25,6 +24,7 @@ module PreviewMarkdown ...@@ -25,6 +24,7 @@ module PreviewMarkdown
} }
} }
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def projects_filter_params def projects_filter_params
{ {
...@@ -32,5 +32,11 @@ module PreviewMarkdown ...@@ -32,5 +32,11 @@ module PreviewMarkdown
suggestions_filter_enabled: params[:preview_suggestions].present? suggestions_filter_enabled: params[:preview_suggestions].present?
} }
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private
# Override this method to customise the markdown for your controller
def preview_markdown_params
{}
end
end end
# frozen_string_literal: true
# Controllers that include this concern must provide:
# * project
# * current_user
module ProjectWikiActions
extend ActiveSupport::Concern
included do
before_action :authorize_read_wiki!
before_action :init_wiki_actions
attr_accessor :project_wiki, :sidebar_page, :sidebar_wiki_entries
end
def init_wiki_actions
load_project_wiki
load_wiki_sidebar
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
redirect_to project_path(project)
end
def load_project_wiki
self.project_wiki = load_wiki
end
def load_wiki_sidebar
self.sidebar_page = project_wiki.find_sidebar(params[:version_id])
return if sidebar_page.present?
# Fallback to default sidebar
self.sidebar_wiki_entries = WikiDirectory.group_by_directory(project_wiki.list_pages(limit: 15))
end
def load_wiki
# Call #wiki to make sure the Wiki Repo is initialized
ProjectWiki.new(project, current_user).tap(&:wiki)
end
end
# frozen_string_literal: true
class Projects::WikiDirectoriesController < Projects::ApplicationController
include ProjectWikiActions
def self.local_prefixes
[controller_path, 'shared/wiki']
end
def show
@wiki_dir = find_dir || WikiDirectory.new(params[:id])
return render('empty') if @wiki_dir.empty?
@wiki_entries = @wiki_pages = Kaminari
.paginate_array(@wiki_dir.pages)
.page(params[:page])
render 'show'
end
private
def find_dir
dir_params = params.permit(:id, :sort, :direction).dup.tap do |h|
Gitlab::Utils.allow_hash_values(h, sort_params_config[:allowed])
end
project_wiki.find_dir(dir_params[:id], dir_params[:sort], dir_params[:direction])
end
end
# frozen_string_literal: true
class Projects::WikiPagesController < Projects::ApplicationController
include ProjectWikiActions
include SendsBlob
include PreviewMarkdown
include Gitlab::Utils::StrongMemoize
def self.local_prefixes
[controller_path, 'shared/wiki']
end
before_action :authorize_create_wiki!, only: [:edit, :create, :update]
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_page, only: [:show, :edit, :update, :history, :destroy]
before_action :valid_encoding?,
if: -> { %w[show edit update].include?(action_name) && load_page }
before_action only: [:edit, :update], unless: :valid_encoding? do
redirect_to(project_wiki_path(@project, @page))
end
def new
redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true)
end
# `#show` handles a number of scenarios:
#
# - If `id` matches a WikiPage, then show the wiki page.
# - If `id` is a file in the wiki repository, then send the file.
# - If we know the user wants to create a new page with the given `id`,
# then display a create form.
# - Otherwise show the empty wiki page and invite the user to create a page.
def show
if @page
show_page
elsif file_blob
show_blob
elsif should_create_missing_page?
create_missing_page
else
render 'missing_page'
end
end
def edit
end
def update
@page = WikiPages::UpdateService
.new(@project, current_user, wiki_params)
.execute(@page)
return saved(:updated) if @page.valid?
render 'edit'
rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
end
def create
@page = WikiPages::CreateService
.new(@project, current_user, wiki_params)
.execute
return saved(:created) if @page.persisted?
render action: "edit"
rescue Gitlab::Git::Wiki::OperationError => e
@page = project_wiki.build_page(wiki_params)
@error = e
render 'edit'
end
def history
if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
total_count: @page.count_versions)
.page(params[:page])
else
redirect_to(
project_wiki_path(@project, :home),
notice: _("Page not found")
)
end
end
def destroy
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to project_wiki_path(@project, :home),
status: 302,
notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
end
private
# Callback for PreviewMarkdown
def preview_markdown_params
{ pipeline: :wiki, project_wiki: project_wiki, page_slug: params[:id] }
end
def show_page
set_encoding_error unless valid_encoding?
@page_dir = @project_wiki.find_dir(@page.directory) if @page.directory.present?
@show_children = true
render 'show'
end
def show_blob
send_blob(@project_wiki.repository, file_blob)
end
def should_create_missing_page?
view_param = @project_wiki.exists? ? 'create' : params[:view]
view_param == 'create' && can?(current_user, :create_wiki, @project)
end
def create_missing_page
# Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
title = params[:id] unless params[:random_title].present?
@page = project_wiki.build_page(title: title)
render 'edit'
end
def wiki_params
params.require(:wiki_page).permit(:title, :content, :format, :message, :last_commit_sha)
end
def load_page
@page ||= @project_wiki.find_page(*page_params)
end
def page_params
keys = [:id]
keys << :version_id if params[:action] == 'show'
params.values_at(*keys)
end
def valid_encoding?
strong_memoize(:valid_encoding) do
@page.content.encoding == Encoding::UTF_8
end
end
def set_encoding_error
flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
end
def file_blob
strong_memoize(:file_blob) do
commit = @project_wiki.repository.commit(@project_wiki.default_branch)
next unless commit
@project_wiki.repository.blob_at(commit.id, params[:id])
end
end
def saved(action)
msg = case action
when :updated
_('Wiki was successfully updated')
when :created
_('Wiki was successfully created')
end
redirect_to(project_wiki_path(@project, @page), notice: msg)
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::WikisController < Projects::ApplicationController class Projects::WikisController < Projects::ApplicationController
include PreviewMarkdown include ProjectWikiActions
include SendsBlob include WikiHelper
include Gitlab::Utils::StrongMemoize
before_action :authorize_read_wiki! def self.local_prefixes
before_action :authorize_create_wiki!, only: [:edit, :create] [controller_path, 'shared/wiki']
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki
before_action :load_page, only: [:show, :edit, :update, :history, :destroy]
before_action :valid_encoding?,
if: -> { %w[show edit update].include?(action_name) && load_page }
before_action only: [:edit, :update], unless: :valid_encoding? do
redirect_to(project_wiki_path(@project, @page))
end
def new
redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true)
end end
def pages def pages
@nesting = show_children_param
@show_children = @nesting != ProjectWiki::NESTING_CLOSED
@wiki_pages = Kaminari.paginate_array( @wiki_pages = Kaminari.paginate_array(
@project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) project_wiki.list_pages(**sort_params)
).page(params[:page]) ).page(params[:page])
@wiki_entries = WikiPage.group_by_directory(@wiki_pages) @wiki_entries = case @nesting
end when ProjectWiki::NESTING_FLAT
@wiki_pages
# `#show` handles a number of scenarios:
#
# - If `id` matches a WikiPage, then show the wiki page.
# - If `id` is a file in the wiki repository, then send the file.
# - If we know the user wants to create a new page with the given `id`,
# then display a create form.
# - Otherwise show the empty wiki page and invite the user to create a page.
def show
if @page
set_encoding_error unless valid_encoding?
render 'show'
elsif file_blob
send_blob(@project_wiki.repository, file_blob)
elsif show_create_form?
# Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
title = params[:id] unless params[:random_title].present?
@page = build_page(title: title)
render 'edit'
else else
render 'empty' WikiDirectory.group_by_directory(@wiki_pages)
end
end
def edit
end
def update
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
if @page.valid?
redirect_to(
project_wiki_path(@project, @page),
notice: _('Wiki was successfully updated.')
)
else
render 'edit'
end
rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
end
def create
@page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
if @page.persisted?
redirect_to(
project_wiki_path(@project, @page),
notice: _('Wiki was successfully updated.')
)
else
render action: "edit"
end
rescue Gitlab::Git::Wiki::OperationError => e
@page = build_page(wiki_params)
@error = e
render 'edit'
end
def history
if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
total_count: @page.count_versions)
.page(params[:page])
else
redirect_to(
project_wiki_path(@project, :home),
notice: _("Page not found")
)
end
end end
def destroy render 'show'
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to project_wiki_path(@project, :home),
status: 302,
notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
end end
def git_access def git_access
...@@ -122,74 +30,13 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -122,74 +30,13 @@ class Projects::WikisController < Projects::ApplicationController
private private
def show_create_form? def sort_params
can?(current_user, :create_wiki, @project) && process_params(sort_params_config)
@page.nil? &&
# Always show the create form when the wiki has had at least one page created.
# Otherwise, we only show the form when the user has navigated from
# the 'empty wiki' page
(@project_wiki.exists? || params[:view] == 'create')
end
def load_project_wiki
@project_wiki = load_wiki
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
@sidebar_page = @project_wiki.find_sidebar(params[:version_id])
unless @sidebar_page # Fallback to default sidebar
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.list_pages(limit: 15))
end
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
redirect_to project_path(@project)
false
end
def load_wiki
ProjectWiki.new(@project, current_user)
end end
def wiki_params def show_children_param
params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) config = nesting_params_config(params[:sort])
end
def build_page(args = {})
WikiPage.new(@project_wiki).tap do |page|
page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
end
end
def load_page process_params(config)
@page ||= @project_wiki.find_page(*page_params)
end
def page_params
keys = [:id]
keys << :version_id if params[:action] == 'show'
params.values_at(*keys)
end
def valid_encoding?
strong_memoize(:valid_encoding) do
@page.content.encoding == Encoding::UTF_8
end
end
def set_encoding_error
flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
end
def file_blob
strong_memoize(:file_blob) do
commit = @project_wiki.repository.commit(@project_wiki.default_branch)
next unless commit
@project_wiki.repository.blob_at(commit.id, params[:id])
end
end end
end end
...@@ -41,6 +41,12 @@ module IconsHelper ...@@ -41,6 +41,12 @@ module IconsHelper
ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url) ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url)
end end
def sprite_icon_with_text(icon_name, content, opts = {})
wrapper_class = opts.delete(:wrapper_class)
icon = sprite_icon(icon_name, opts)
content_tag(:span, [icon, content].join('').html_safe, class: wrapper_class)
end
def sprite_icon(icon_name, size: nil, css_class: nil) def sprite_icon(icon_name, size: nil, css_class: nil)
if Gitlab::Sentry.should_raise_for_dev? if Gitlab::Sentry.should_raise_for_dev?
unless known_sprites.include?(icon_name) unless known_sprites.include?(icon_name)
......
...@@ -48,14 +48,23 @@ module WikiHelper ...@@ -48,14 +48,23 @@ module WikiHelper
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) WIKI_SORT_CSS_CLASSES = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
sort ||= ProjectWiki::TITLE_ORDER
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' def wiki_sort_controls(sort_params = {}, &block)
reversed_direction = direction == 'desc' ? 'asc' : 'desc' current_sort = sort_params[:sort] || ProjectWiki::TITLE_ORDER
icon_class = direction == 'desc' ? 'highest' : 'lowest' current_direction = (sort_params[:direction] || 'asc').inquiry
link_to(project_wikis_pages_path(project, sort: sort, direction: reversed_direction), reversed_direction = current_direction.desc? ? 'asc' : 'desc'
type: 'button', class: link_class, title: _('Sort direction')) do icon_class = current_direction.desc? ? 'highest' : 'lowest'
sorting = sort_params.merge(sort: current_sort, direction: reversed_direction)
opts = {
type: 'button',
class: WIKI_SORT_CSS_CLASSES,
title: _('Sort direction')
}
link_to(yield(sorting), opts) do
sprite_icon("sort-#{icon_class}", size: 16) sprite_icon("sort-#{icon_class}", size: 16)
end end
end end
...@@ -67,4 +76,86 @@ module WikiHelper ...@@ -67,4 +76,86 @@ module WikiHelper
s_("Wiki|Title") s_("Wiki|Title")
end end
end end
# Render the sprite icon given the current show_children state
def wiki_show_children_icon(nesting)
icon_name, icon_text =
case nesting
when ProjectWiki::NESTING_TREE
['folder-open', s_("Wiki|Show folder contents")]
when ProjectWiki::NESTING_CLOSED
['folder-o', s_("Wiki|Hide folder contents")]
else
['list-bulleted', s_("Wiki|Show files separately")]
end
sprite_icon_with_text(icon_name, icon_text, size: 16)
end
def wiki_page_link(wiki_page, nesting, project)
link = link_to(wiki_page.title,
project_wiki_path(project, wiki_page),
class: 'wiki-page-title')
case nesting
when ProjectWiki::NESTING_FLAT
tags = []
if wiki_page.directory.present?
wiki_dir = WikiDirectory.new(wiki_page.directory)
tags << link_to(wiki_dir.slug, project_wiki_dir_path(project, wiki_dir), class: 'wiki-page-dir-name')
tags << content_tag(:span, '/', class: 'wiki-page-name-separator')
end
tags << link
tags.join.html_safe
else
link
end
end
def sort_params_config
{
keys: [:sort, :direction],
defaults: {
sort: ProjectWiki::TITLE_ORDER, direction: ProjectWiki::DIRECTION_ASC
},
allowed: {
sort: ProjectWiki::SORT_ORDERS, direction: ProjectWiki::SORT_DIRECTIONS
}
}
end
def nesting_params_config(sort_key)
default_val = case sort_key
when ProjectWiki::CREATED_AT_ORDER
ProjectWiki::NESTING_FLAT
else
ProjectWiki::NESTING_CLOSED
end
{
keys: [:show_children],
defaults: { show_children: default_val },
allowed: { show_children: ProjectWiki::NESTINGS }
}
end
def process_params(config)
unprocessed = params.permit(*config[:keys])
processed = unprocessed
.with_defaults(config[:defaults])
.tap { |h| Gitlab::Utils.allow_hash_values(h, config[:allowed]) }
.to_hash
.transform_keys(&:to_sym)
if processed.keys == config[:keys]
processed.size == 1 ? processed.values.first : processed
else
raise ActionController::BadRequest, "illegal parameters: #{unprocessed}"
end
end
def home_page?
params[:id] == 'home'
end
end end
...@@ -17,6 +17,13 @@ class ProjectWiki ...@@ -17,6 +17,13 @@ class ProjectWiki
CREATED_AT_ORDER = 'created_at' CREATED_AT_ORDER = 'created_at'
DIRECTION_DESC = 'desc' DIRECTION_DESC = 'desc'
DIRECTION_ASC = 'asc' DIRECTION_ASC = 'asc'
SORT_ORDERS = [TITLE_ORDER, CREATED_AT_ORDER].freeze
SORT_DIRECTIONS = [DIRECTION_ASC, DIRECTION_DESC].freeze
NESTING_FLAT = 'flat'
NESTING_TREE = 'tree'
NESTING_CLOSED = 'hidden'
NESTINGS = [NESTING_TREE, NESTING_CLOSED, NESTING_FLAT].freeze
# Returns a string describing what went wrong after # Returns a string describing what went wrong after
# an operation fails. # an operation fails.
...@@ -58,7 +65,11 @@ class ProjectWiki ...@@ -58,7 +65,11 @@ class ProjectWiki
end end
def wiki_base_path def wiki_base_path
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') ::File.join(project_base_path, 'wikis')
end
def wiki_page_path
::File.join(project_base_path, '-', 'wiki_pages')
end end
# Returns the Gitlab::Git::Wiki object. # Returns the Gitlab::Git::Wiki object.
...@@ -125,6 +136,23 @@ class ProjectWiki ...@@ -125,6 +136,23 @@ class ProjectWiki
end end
end end
# Finds directory within the repository based on a slug
#
# dir_name - The directory prefix.
#
# Returns an initialized WikiDirectory instance or nil
def find_dir(dir_name, sort = nil, direction = DIRECTION_ASC)
descending = direction == DIRECTION_DESC
# WikiListPagesRequest currently does not support server-side
# filtering. Ideally this logic should be moved to the gitaly
# side.
pages = wiki
.list_pages(sort: sort, direction_desc: descending)
.map { |page| WikiPage.new(self, page, true) }
.select { |wp| wp.directory == dir_name }
WikiDirectory.new(dir_name, pages) if pages.present?
end
def find_sidebar(version = nil) def find_sidebar(version = nil)
find_page(SIDEBAR, version) find_page(SIDEBAR, version)
end end
...@@ -144,6 +172,12 @@ class ProjectWiki ...@@ -144,6 +172,12 @@ class ProjectWiki
false false
end end
def build_page(attrs)
WikiPage.new(self).tap do |page|
page.update_attributes(attrs) # rubocop:disable Rails/ActiveRecordAliases
end
end
def update_page(page, content:, title: nil, format: :markdown, message: nil) def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title) commit = commit_details(:updated, message, page.title)
...@@ -171,7 +205,7 @@ class ProjectWiki ...@@ -171,7 +205,7 @@ class ProjectWiki
title_array = title.split("/") title_array = title.split("/")
title = title_array.pop title = title_array.pop
[title, title_array.join("/")] [title, ::File.join(title_array)]
end end
def repository def repository
...@@ -198,6 +232,10 @@ class ProjectWiki ...@@ -198,6 +232,10 @@ class ProjectWiki
private private
def project_base_path
::File.join(Gitlab.config.gitlab.relative_url_root, @project.full_path)
end
def create_repo!(raw_repository) def create_repo!(raw_repository)
gitlab_shell.create_wiki_repository(project) gitlab_shell.create_wiki_repository(project)
......
# frozen_string_literal: true # frozen_string_literal: true
class WikiDirectory class WikiDirectory
include StaticModel
include ActiveModel::Validations include ActiveModel::Validations
attr_accessor :slug, :pages attr_accessor :slug, :pages
validates :slug, presence: true validates :slug, presence: true
# StaticModel overrides and configuration:
def self.primary_key
'slug'
end
def id
"#{slug}@#{last_version&.sha}"
end
def self.model_name
ActiveModel::Name.new(self, nil, 'wiki_dir')
end
alias_method :to_param, :slug
alias_method :title, :slug
# Sorts and groups pages by directory.
#
# pages - an array of WikiPage objects.
#
# Returns an array of WikiPage and WikiDirectory objects.
# The entries are sorted in the order of the input array, where
# directories appear in the position of their first member.
def self.group_by_directory(pages)
grouped = []
dirs = Hash.new do |h, k|
new(k).tap { |dir| grouped << (h[k] = dir) }
end
Array.wrap(pages).each_with_object(grouped) do |page, top_level|
group = page.directory.present? ? dirs[page.directory] : top_level
group << page
end
end
def initialize(slug, pages = []) def initialize(slug, pages = [])
@slug = slug @slug = slug
@pages = pages @pages = pages
end end
# Relative path to the partial to be used when rendering collections def <<(page)
# of this object. @pages << page
def to_partial_path @last_version = nil
'projects/wikis/wiki_directory' end
def last_version
@last_version ||= @pages.map(&:last_version).max_by(&:authored_date)
end
def page_count
@pages.size
end
def empty?
page_count.zero?
end
def to_partial_path(context = nil)
name = [context, 'wiki_directory'].compact.join('_')
"projects/wiki_directories/#{name}"
end end
end end
...@@ -15,30 +15,7 @@ class WikiPage ...@@ -15,30 +15,7 @@ class WikiPage
end end
def self.model_name def self.model_name
ActiveModel::Name.new(self, nil, 'wiki') ActiveModel::Name.new(self, nil, 'wiki_page')
end
# Sorts and groups pages by directory.
#
# pages - an array of WikiPage objects.
#
# Returns an array of WikiPage and WikiDirectory objects. The entries are
# sorted by alphabetical order (directories and pages inside each directory).
# Pages at the root level come before everything.
def self.group_by_directory(pages)
return [] if pages.blank?
pages.each_with_object([]) do |page, grouped_pages|
next grouped_pages << page unless page.directory.present?
directory = grouped_pages.find do |obj|
obj.is_a?(WikiDirectory) && obj.slug == page.directory
end
next directory.pages << page if directory
grouped_pages << WikiDirectory.new(page.directory, [page])
end
end end
def self.unhyphenize(name) def self.unhyphenize(name)
...@@ -66,6 +43,16 @@ class WikiPage ...@@ -66,6 +43,16 @@ class WikiPage
Gitlab::HookData::WikiPageBuilder.new(self).build Gitlab::HookData::WikiPageBuilder.new(self).build
end end
# Create a new WikiPage
#
# == Parameters:
# wiki::
# A `ProjectWiki` model object
# page::
# A `Gitlab::Git::WikiPage` business object, to which this class provides a facade
# persisted::
# Is this page fully saved on disk?
#
def initialize(wiki, page = nil, persisted = false) def initialize(wiki, page = nil, persisted = false)
@wiki = wiki @wiki = wiki
@page = page @page = page
...@@ -250,10 +237,10 @@ class WikiPage ...@@ -250,10 +237,10 @@ class WikiPage
end end
end end
# Relative path to the partial to be used when rendering collections def to_partial_path(context = nil)
# of this object. name = [context, 'wiki_page'].compact.join('_')
def to_partial_path
'projects/wikis/wiki_page' "projects/wiki_pages/#{name}"
end end
def id def id
......
...@@ -282,14 +282,14 @@ ...@@ -282,14 +282,14 @@
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home) - wiki_url = project_wiki_path(@project, :home)
= nav_link(controller: :wikis) do = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories]) do
= link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('book') = sprite_icon('book')
%span.nav-item-name %span.nav-item-name
= _('Wiki') = _('Wiki')
%ul.sidebar-sub-level-items.is-fly-out-only %ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories], html_options: { class: "fly-out-top-item" } ) do
= link_to wiki_url do = link_to wiki_url do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Wiki') = _('Wiki')
......
%li
%span.text-secondary-500.svg-icon.svg-baseline
- if @show_children
= sprite_icon('folder-open', size: 16)
- else
= sprite_icon('folder-o', size: 16)
= link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir)
- unless @show_children
%span.badge.badge-pill.wiki-dir-page-count= wiki_dir.page_count
.float-right
%small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_dir.last_version.authored_date) }).html_safe
- if @show_children
%ul
= render wiki_dir.pages, context: context
%li
%span.text-secondary-300.svg-icon.svg-baseline
= sprite_icon('folder-open', size: 16)
= link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir)
%ul= render wiki_dir.pages, context: context
= render wiki_directory.to_partial_path(context), wiki_dir: wiki_directory, context: context
- layout_path = 'shared/empty_states/wikis_layout'
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project)
- breadcrumb_title s_(@wiki_dir.slug)
- page_title @wiki_dir.slug
- if can?(current_user, :create_wiki, @project)
- create_path = project_wiki_path(@project, params[:id], { view: 'create', params: { title: "#{params[:id]}/" } })
- create_link = link_to s_('WikiDirEmpty|Create a page in this directory'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiDirEmpty|Create a page')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
%h4.text-left
= s_('WikiDirEmpty|This directory has no wiki pages')
%p.text-left
= s_("WikiDirEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
= create_link
- elsif can?(current_user, :read_issue, @project)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
- new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-success', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4
= s_('WikiDirEmpty|This directory has no wiki pages')
%p.text-left
= s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link }
= new_issue_link
- else
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4
= s_('WikiDirEmpty|This directory has no wiki pages')
%p
= s_('WikiEmpty|You must be a project member in order to add wiki pages.')
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project)
- breadcrumb_title s_(@wiki_dir.slug)
- page_title @wiki_dir.slug
= render 'page_listing', { allow_change_nesting: false, wiki_page_title: page_title, page_path: ->(opts) { project_wiki_dir_path(@project, @wiki_dir, opts) } }
- form_classes = 'wiki-form common-note-form prepend-top-default js-quick-submit' - form_classes = 'wiki-form common-note-form prepend-top-default js-quick-submit'
- form_classes += ' js-new-wiki-page' unless @page.persisted? - form_classes += ' js-new-wiki-page' unless @page.persisted?
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, = form_for [@project.namespace.becomes(Namespace), @project, @page],
method: @page.persisted? ? 'put' : 'post',
url: { controller: 'wiki_pages', action: @page.persisted? ? :update : :create },
html: { class: form_classes }, html: { class: form_classes },
data: { uploads_path: uploads_path } do |f| data: { uploads_path: uploads_path } do |f|
= form_errors(@page) = form_errors(@page)
...@@ -12,7 +14,7 @@ ...@@ -12,7 +14,7 @@
.form-group.row .form-group.row
.col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12 .col-sm-12
= f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: _('Wiki|Page title') = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: s_('Wiki|Page title')
%span.d-inline-block.mw-100.prepend-top-5 %span.d-inline-block.mw-100.prepend-top-5
= icon('lightbulb-o') = icon('lightbulb-o')
- if @page.persisted? - if @page.persisted?
......
= link_to @page.human_title, project_wiki_path(@project, @page)
%span.light
= _('&middot;').html_safe
= subtitle
%li %li
= link_to wiki_page.title, project_wiki_path(@project, wiki_page) %span.text-secondary-500.svg-icon.svg-baseline= sprite_icon('book', size: 16)
%small (#{wiki_page.format}) = wiki_page_link(wiki_page, @nesting, @project)
.float-right .float-right
%span.badge.badge-pill.wiki-page-format= _(wiki_page.format)
- if wiki_page.last_version - if wiki_page.last_version
= '/'
%small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe
- is_active = params[:id] == wiki_page.slug
- icon_active_class = is_active ? 'text-secondary-800' : 'text-secondary-300'
%li{ class: active_when(is_active) }
%span.svg-icon.svg-baseline{ class: icon_active_class }
= sprite_icon('book', size: 16)
- if is_active
= wiki_page.human_title
- else
= link_to project_wiki_path(@project, wiki_page) do
= wiki_page.human_title
= render wiki_page.to_partial_path(context), wiki_page: wiki_page
- @content_class = "limit-container-width" unless fluid_layout - @content_class = 'edit-wiki-page' + (fluid_layout ? '' : ' limit-container-width')
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page) - add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project)
- if @page.persisted? && @page_dir.present?
- add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir)
- if @page.persisted?
- add_to_breadcrumbs @page.human_title, project_wiki_path(@project, @page)
- breadcrumb_title @page.persisted? ? _("Edit") : _("New") - breadcrumb_title @page.persisted? ? _("Edit") : _("New")
- page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki") - page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki")
...@@ -12,10 +17,7 @@ ...@@ -12,10 +17,7 @@
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
- if @page.persisted? - if @page.persisted?
= link_to @page.human_title, project_wiki_path(@project, @page) = render partial: 'page_title', locals: { subtitle: s_("Wiki|Edit Page") }
%span.light
&middot;
= s_("Wiki|Edit Page")
- else - else
= s_("Wiki|Create New Page") = s_("Wiki|Create New Page")
......
- @content_class = 'wiki-history'
- page_title _("History"), @page.human_title, _("Wiki") - page_title _("History"), @page.human_title, _("Wiki")
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
...@@ -6,10 +7,7 @@ ...@@ -6,10 +7,7 @@
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
= link_to @page.human_title, project_wiki_path(@project, @page) = render partial: 'page_title', locals: { subtitle: _("History") }
%span.light
&middot;
= _("History")
.table-holder .table-holder
%table.table %table.table
...@@ -39,4 +37,4 @@ ...@@ -39,4 +37,4 @@
= version.format = version.format
= paginate @page_versions, theme: 'gitlab' = paginate @page_versions, theme: 'gitlab'
= render 'sidebar' = render 'shared/wiki/sidebar'
- @content_class = 'wiki-page' + (fluid_layout ? '' : ' limit-container-width')
- breadcrumb_title @page.human_title
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project)
- if @page_dir.present?
- add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir)
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
.nav-text.flex-fill
%h2.wiki-page-title= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.last_version.authored_date)}
.nav-controls.pb-md-3.pb-lg-0
= render 'main_links'
- if @page.historical?
.warning_message
= s_("WikiHistoricalPage|This is an old version of this page.")
- most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page)
- history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page)
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
.md.md-file.qa-wiki-page-content
= render_wiki_content(@page)
= render 'sidebar'
%li{ class: active_when(params[:id] == wiki_page.slug) }
= link_to project_wiki_path(@project, wiki_page) do
= wiki_page.human_title
%li
= wiki_directory.slug
%ul
= render wiki_directory.pages, context: context
= render "#{context}_wiki_page", wiki_page: wiki_page
- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
- sort_title = wiki_sort_title(params[:sort])
.wiki-page-header.top-area.flex-column.flex-lg-row
.nav-text.flex-fill
%h2.wiki-page-title
= s_("Wiki|Wiki Pages")
.nav-controls.pb-md-3.pb-lg-0
= link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
= _("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
= render @wiki_entries, context: 'pages'
= paginate @wiki_pages, theme: 'gitlab'
- @content_class = "limit-container-width" unless fluid_layout - add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title @page.human_title - breadcrumb_title s_("Wiki|Pages")
- wiki_breadcrumb_dropdown_links(@page.slug) - page_title s_("Wiki|Contents"), _("Wiki")
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = render 'page_listing', { allow_change_nesting: ::Feature.enabled?(:wikis_allow_change_nesting), wiki_page_title: page_title, page_path: ->(opts) { project_wikis_pages_path(@project, opts) } }
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
.nav-text.flex-fill
%h2.wiki-page-title= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.last_version.authored_date)}
.nav-controls.pb-md-3.pb-lg-0
= render 'main_links'
- if @page.historical?
.warning_message
= s_("WikiHistoricalPage|This is an old version of this page.")
- most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page)
- history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page)
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
.md.md-file{ data: { qa_selector: 'wiki_page_content' } }
= render_wiki_content(@page)
= render 'sidebar'
- layout_path = 'shared/empty_states/wikis_layout' - layout_path = 'shared/empty_states/wikis_layout'
- wiki_is_empty = @project_wiki.empty?
- empty_msg = wiki_is_empty ? s_('WikiEmpty|This project has no wiki pages') : s_('WikiEmpty|This page does not exist')
- create_msg = wiki_is_empty ? s_('WikiEmpty|Create your first page') : s_('WikiEmpty|Create this page')
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
- create_path = project_wiki_path(@project, params[:id], { view: 'create' }) - create_path = project_wiki_path(@project, params[:id], { view: 'create' })
- create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiEmpty|Create your first page') - create_link = link_to create_msg, create_path, class: 'btn btn-success qa-create-first-page-link', title: create_msg
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
%h4.text-left %h4.text-left
...@@ -17,7 +20,7 @@ ...@@ -17,7 +20,7 @@
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4 %h4
= s_('WikiEmpty|This project has no wiki pages') = empty_msg
%p.text-left %p.text-left
= s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link }
= new_issue_link = new_issue_link
...@@ -25,6 +28,6 @@ ...@@ -25,6 +28,6 @@
- else - else
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4 %h4
= s_('WikiEmpty|This project has no wiki pages') = empty_msg
%p %p
= s_('WikiEmpty|You must be a project member in order to add wiki pages.') = s_('WikiEmpty|You must be a project member in order to add wiki pages.')
- if (@page && @page.persisted?) - if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do = link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do
= s_("Wiki|New page") = s_("Wiki|New page")
= link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do
= s_("Wiki|Page history") = s_("Wiki|Page history")
......
- @no_container = true
- current_sorting = params.permit(:sort, :direction)
- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class }
.wiki-page-header.top-area.flex-column.flex-lg-row
.nav-text.flex-fill
%h2.wiki-page-title
= wiki_page_title
.nav-controls.pb-md-3.pb-lg-0
- if can?(current_user, :create_wiki, @project)
= link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success" do
= s_("Wiki|New page")
= link_to project_wikis_git_access_path(@project), class: 'btn qa-clone-repository-link' do
= sprite_icon('download', size: 16)
= _("Clone repository")
- if @nesting.present? && allow_change_nesting
.dropdown.inline.wiki-nesting-dropdown
.btn-group{ role: 'group' }
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
= wiki_show_children_icon(@nesting)
= sprite_icon('chevron-down', size: 16)
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
- ProjectWiki::NESTINGS.each do |choice|
%li= link_to wiki_show_children_icon(choice), page_path.call(current_sorting.merge(show_children: choice)), class: @nesting == choice ? 'is-active' : ''
.dropdown.inline.wiki-sort-dropdown
.btn-group{ role: 'group' }
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
= sort_title
= sprite_icon('chevron-down', size: 16)
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= sortable_item(s_("Wiki|Title"), page_path.call(sort: ProjectWiki::TITLE_ORDER), sort_title)
= sortable_item(s_("Wiki|Created date"), page_path.call(sort: ProjectWiki::CREATED_AT_ORDER), sort_title)
= wiki_sort_controls(current_sorting.merge(show_children: @nesting), &page_path)
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'
= paginate @wiki_pages, theme: 'gitlab'
---
title: Sort wiki pages by date
merge_request: 30245
type: added
...@@ -615,7 +615,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -615,7 +615,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
# Since both wiki and repository routing contains wildcard characters # Since both wiki and repository routing contains wildcard characters
# its preferable to keep it below all other project routes # its preferable to keep them below all other project routes
draw :wiki draw :wiki
draw :repository draw :repository
......
scope(controller: :wikis) do scope(controller: :wikis) do
scope(path: 'wikis/pages', as: :wiki_pages, format: false) do
get :new, to: 'wiki_pages#new'
post '/', to: 'wiki_pages#create'
end
scope(path: 'wikis', as: :wikis) do scope(path: 'wikis', as: :wikis) do
get :git_access get :git_access
get :pages get :pages
get :new get '/', to: redirect('%{namespace_id}/%{project_id}/-/wiki_pages/home')
get '/', to: redirect('%{namespace_id}/%{project_id}/wikis/home') get '/*id', to: redirect('%{namespace_id}/%{project_id}/-/wiki_pages/%{id}')
post '/', to: 'wikis#create' end
scope(path: '-/wiki_pages', as: :wiki_page, format: false) do
post '/', to: 'wiki_pages#create'
end end
scope(path: 'wikis/*id', as: :wiki, format: false) do scope(path: '-/wiki_pages/*id', as: :wiki, format: false, controller: :wiki_pages) do
get :edit get :edit
get :history get :history
post :preview_markdown post :preview_markdown
...@@ -15,4 +23,8 @@ scope(controller: :wikis) do ...@@ -15,4 +23,8 @@ scope(controller: :wikis) do
put '/', action: :update put '/', action: :update
delete '/', action: :destroy delete '/', action: :destroy
end end
scope(path: '-/wiki_dirs/*id', as: :wiki_dir, format: false, controller: :wiki_directories) do
get '/', action: :show
end
end end
...@@ -6,7 +6,7 @@ module Banzai ...@@ -6,7 +6,7 @@ module Banzai
class Rewriter class Rewriter
def initialize(link_string, wiki:, slug:) def initialize(link_string, wiki:, slug:)
@uri = Addressable::URI.parse(link_string) @uri = Addressable::URI.parse(link_string)
@wiki_base_path = wiki && wiki.wiki_base_path @wiki_base_path = wiki && wiki.wiki_page_path
@slug = slug @slug = slug
end end
......
...@@ -130,5 +130,15 @@ module Gitlab ...@@ -130,5 +130,15 @@ module Gitlab
IPAddr.new(str) IPAddr.new(str)
rescue IPAddr::InvalidAddressError rescue IPAddr::InvalidAddressError
end end
# Filter a Hash against a mapping of keys to sets of allowed values.
#
# Keys that do not pass the filter will be removed from the Hash.
# This mutates the input hash.
def allow_hash_values(hash, allowed)
allowed.each do |key, allowed_values|
hash.delete(key) if hash.key?(key) && !allowed_values.include?(hash[key])
end
end
end end
end end
...@@ -411,6 +411,9 @@ msgstr "" ...@@ -411,6 +411,9 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time." msgid "%{verb} %{time_spent_value} spent time."
msgstr "" msgstr ""
msgid "&middot;"
msgstr ""
msgid "'%{level}' is not a valid visibility level" msgid "'%{level}' is not a valid visibility level"
msgstr "" msgstr ""
...@@ -18704,7 +18707,10 @@ msgstr "" ...@@ -18704,7 +18707,10 @@ msgstr ""
msgid "Wiki pages" msgid "Wiki pages"
msgstr "" msgstr ""
msgid "Wiki was successfully updated." msgid "Wiki was successfully created"
msgstr ""
msgid "Wiki was successfully updated"
msgstr "" msgstr ""
msgid "WikiClone|Clone your wiki" msgid "WikiClone|Clone your wiki"
...@@ -18722,6 +18728,18 @@ msgstr "" ...@@ -18722,6 +18728,18 @@ msgstr ""
msgid "WikiClone|Start Gollum and edit locally" msgid "WikiClone|Start Gollum and edit locally"
msgstr "" msgstr ""
msgid "WikiDirEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr ""
msgid "WikiDirEmpty|Create a page"
msgstr ""
msgid "WikiDirEmpty|Create a page in this directory"
msgstr ""
msgid "WikiDirEmpty|This directory has no wiki pages"
msgstr ""
msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
msgstr "" msgstr ""
...@@ -18740,6 +18758,9 @@ msgstr "" ...@@ -18740,6 +18758,9 @@ msgstr ""
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on." msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "" msgstr ""
msgid "WikiEmpty|Create this page"
msgstr ""
msgid "WikiEmpty|Create your first page" msgid "WikiEmpty|Create your first page"
msgstr "" msgstr ""
...@@ -18749,6 +18770,9 @@ msgstr "" ...@@ -18749,6 +18770,9 @@ msgstr ""
msgid "WikiEmpty|The wiki lets you write documentation for your project" msgid "WikiEmpty|The wiki lets you write documentation for your project"
msgstr "" msgstr ""
msgid "WikiEmpty|This page does not exist"
msgstr ""
msgid "WikiEmpty|This project has no wiki pages" msgid "WikiEmpty|This project has no wiki pages"
msgstr "" msgstr ""
...@@ -18803,6 +18827,9 @@ msgstr "" ...@@ -18803,6 +18827,9 @@ msgstr ""
msgid "WikiPage|Write your content or drag files here…" msgid "WikiPage|Write your content or drag files here…"
msgstr "" msgstr ""
msgid "Wiki|Contents"
msgstr ""
msgid "Wiki|Create New Page" msgid "Wiki|Create New Page"
msgstr "" msgstr ""
...@@ -18815,6 +18842,9 @@ msgstr "" ...@@ -18815,6 +18842,9 @@ msgstr ""
msgid "Wiki|Edit Page" msgid "Wiki|Edit Page"
msgstr "" msgstr ""
msgid "Wiki|Hide folder contents"
msgstr ""
msgid "Wiki|More Pages" msgid "Wiki|More Pages"
msgstr "" msgstr ""
...@@ -18833,10 +18863,13 @@ msgstr "" ...@@ -18833,10 +18863,13 @@ msgstr ""
msgid "Wiki|Pages" msgid "Wiki|Pages"
msgstr "" msgstr ""
msgid "Wiki|Title" msgid "Wiki|Show files separately"
msgstr "" msgstr ""
msgid "Wiki|Wiki Pages" msgid "Wiki|Show folder contents"
msgstr ""
msgid "Wiki|Title"
msgstr "" msgstr ""
msgid "Will deploy to" msgid "Will deploy to"
......
...@@ -5,7 +5,7 @@ module QA ...@@ -5,7 +5,7 @@ module QA
module Project module Project
module Wiki module Wiki
class Edit < Page::Base class Edit < Page::Base
view 'app/views/projects/wikis/_main_links.html.haml' do view 'app/views/shared/wiki/_main_links.html.haml' do
element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern
element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern
element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
......
...@@ -7,7 +7,7 @@ module QA ...@@ -7,7 +7,7 @@ module QA
class New < Page::Base class New < Page::Base
include Component::LazyLoader include Component::LazyLoader
view 'app/views/projects/wikis/_form.html.haml' do view 'app/views/projects/wiki_pages/_form.html.haml' do
element :wiki_title_textbox element :wiki_title_textbox
element :wiki_content_textarea element :wiki_content_textarea
element :wiki_message_textbox element :wiki_message_textbox
......
...@@ -7,11 +7,11 @@ module QA ...@@ -7,11 +7,11 @@ module QA
class Show < Page::Base class Show < Page::Base
include Page::Component::LegacyClonePanel include Page::Component::LegacyClonePanel
view 'app/views/projects/wikis/pages.html.haml' do view 'app/views/shared/wiki/_page_listing.html.haml' do
element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
end end
view 'app/views/projects/wikis/show.html.haml' do view 'app/views/projects/wiki_pages/show.html.haml' do
element :wiki_page_content element :wiki_page_content
end end
......
...@@ -13,7 +13,7 @@ module QA ...@@ -13,7 +13,7 @@ module QA
resource.message = 'Update home' resource.message = 'Update home'
end end
validate_content('My First Wiki Content') validate_created('My First Wiki Content')
Page::Project::Wiki::Edit.perform(&:click_edit) Page::Project::Wiki::Edit.perform(&:click_edit)
Page::Project::Wiki::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName Page::Project::Wiki::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
...@@ -21,7 +21,7 @@ module QA ...@@ -21,7 +21,7 @@ module QA
page.save_changes page.save_changes
end end
validate_content('My Second Wiki Content') validate_edited('My Second Wiki Content')
Resource::Repository::WikiPush.fabricate! do |push| Resource::Repository::WikiPush.fabricate! do |push|
push.wiki = wiki push.wiki = wiki
...@@ -34,7 +34,12 @@ module QA ...@@ -34,7 +34,12 @@ module QA
expect(page).to have_content('My Third Wiki Content') expect(page).to have_content('My Third Wiki Content')
end end
def validate_content(content) def validate_created(content)
expect(page).to have_content('Wiki was successfully created')
expect(page).to have_content(/#{content}/)
end
def validate_edited(content)
expect(page).to have_content('Wiki was successfully updated') expect(page).to have_content('Wiki was successfully updated')
expect(page).to have_content(/#{content}/) expect(page).to have_content(/#{content}/)
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::WikiDirectoriesController do
set(:project) { create(:project, :public, :repository) }
let(:user) { project.owner }
let(:project_wiki) { ProjectWiki.new(project, user) }
let(:wiki) { project_wiki.wiki }
let(:dir_slug) { 'the-directory' }
let(:dir_contents) { [create(:wiki_page)] }
let(:the_dir) { WikiDirectory.new(dir_slug, dir_contents) }
before do
allow(controller).to receive(:find_dir).and_return(the_dir)
sign_in(user)
end
describe 'GET #show' do
let(:show_params) do
{
namespace_id: project.namespace,
project_id: project,
id: dir_slug
}
end
before do
get :show, params: show_params
end
context 'the directory is empty' do
let(:the_dir) { nil }
it { is_expected.to render_template('empty') }
end
context 'the directory does exist' do
it { is_expected.to render_template('show') }
it 'sets the wiki_dir attribute' do
expect(assigns(:wiki_dir)).to eq(the_dir)
end
it 'assigns the wiki pages' do
expect(assigns(:wiki_pages)).to eq(dir_contents)
end
end
end
end
This diff is collapsed.
...@@ -4,10 +4,10 @@ require 'spec_helper' ...@@ -4,10 +4,10 @@ require 'spec_helper'
describe Projects::WikisController do describe Projects::WikisController do
let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:project) { create(:project, :public, :repository) }
let(:user) { project.owner } let_it_be(:user) { project.owner }
let(:project_wiki) { ProjectWiki.new(project, user) } let_it_be(:project_wiki) { ProjectWiki.new(project, user) }
let(:wiki) { project_wiki.wiki } let_it_be(:wiki) { project_wiki.wiki }
let(:wiki_title) { 'page title test' } let_it_be(:wiki_title) { 'page title test' }
before do before do
create_page(wiki_title, 'hello world') create_page(wiki_title, 'hello world')
...@@ -19,231 +19,86 @@ describe Projects::WikisController do ...@@ -19,231 +19,86 @@ describe Projects::WikisController do
destroy_page(wiki_title) destroy_page(wiki_title)
end end
describe 'GET #new' do describe 'GET #pages' do
subject { get :new, params: { namespace_id: project.namespace, project_id: project } } subject do
get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }.merge(extra_params)
it 'redirects to #show and appends a `random_title` param' do
subject
expect(response).to have_http_status(302)
expect(Rails.application.routes.recognize_path(response.redirect_url)).to include(
controller: 'projects/wikis',
action: 'show'
)
expect(response.redirect_url).to match(/\?random_title=true\Z/)
end
end end
describe 'GET #pages' do let(:extra_params) { {} }
subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
it 'does not load the pages content' do it 'does not load the pages content' do
expect(controller).to receive(:load_wiki).and_return(project_wiki) expect(controller).to receive(:load_wiki).and_return(project_wiki)
expect(project_wiki).to receive(:list_pages).twice.and_call_original expect(project_wiki).to receive(:list_pages).twice.and_call_original
subject subject
end end
end
describe 'GET #history' do describe 'illegal params' do
before do shared_examples :a_bad_request do
allow(controller) it do
.to receive(:can?) expect { subject }.to raise_error(ActionController::BadRequest)
.with(any_args)
.and_call_original
# The :create_wiki permission is irrelevant to reading history.
expect(controller)
.not_to receive(:can?)
.with(anything, :create_wiki, any_args)
allow(controller)
.to receive(:can?)
.with(anything, :read_wiki, any_args)
.and_return(allow_read_wiki)
end end
shared_examples 'fetching history' do |expected_status|
before do
get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }
end end
it "returns status #{expected_status}" do describe ':sort' do
expect(response).to have_http_status(expected_status) let(:extra_params) { { sort: 'wibble' } }
end
end
it_behaves_like 'fetching history', :ok do
let(:allow_read_wiki) { true }
it 'assigns @page_versions' do
expect(assigns(:page_versions)).to be_present
end
end
it_behaves_like 'fetching history', :not_found do it_behaves_like :a_bad_request
let(:allow_read_wiki) { false }
end
end end
describe 'GET #show' do describe ':direction' do
render_views let(:extra_params) { { direction: 'wibble' } }
let(:random_title) { nil }
subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: id, random_title: random_title } }
context 'when page exists' do
let(:id) { wiki_title }
it 'limits the retrieved pages for the sidebar' do it_behaves_like :a_bad_request
expect(controller).to receive(:load_wiki).and_return(project_wiki)
expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
subject
expect(response).to have_http_status(:ok)
expect(assigns(:page).title).to eq(wiki_title)
end end
context 'when page content encoding is invalid' do describe ':show_children' do
it 'sets flash error' do let(:extra_params) { { show_children: 'wibble' } }
allow(controller).to receive(:valid_encoding?).and_return(false)
subject it_behaves_like :a_bad_request
expect(response).to have_http_status(:ok)
expect(flash[:notice]).to eq('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
end
end end
end end
context 'when the page does not exist' do shared_examples 'sorting-and-nesting' do |sort_key, default_nesting|
let(:id) { 'does not exist' } context "the user is sorting by #{sort_key}" do
let(:extra_params) { sort_params.merge(nesting_params) }
let(:sort_params) { { sort: sort_key } }
let(:nesting_params) { {} }
before do before do
subject subject
end end
it 'builds a new wiki page with the id as the title' do it "sets nesting to #{default_nesting} by default" do
expect(assigns(:page).title).to eq(id) expect(assigns :nesting).to eq default_nesting
end end
context 'when a random_title param is present' do it 'hides children if the default requires it' do
let(:random_title) { true } expect(assigns :show_children).to be(default_nesting != ProjectWiki::NESTING_CLOSED)
it 'builds a new wiki page with no title' do
expect(assigns(:page).title).to be_empty
end
end
end end
context 'when page is a file' do ProjectWiki::NESTINGS.each do |nesting|
include WikiHelpers context "the user explicitly passes show_children = #{nesting}" do
let(:nesting_params) { { show_children: nesting } }
let(:id) { upload_file_to_wiki(project, user, file_name) } it 'sets nesting to the provided value' do
expect(assigns :nesting).to eq nesting
before do
subject
end
context 'when file is an image' do
let(:file_name) { 'dk.png' }
it 'delivers the image' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
context 'when file is a svg' do
let(:file_name) { 'unsanitized.svg' }
it 'delivers the image' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end end
end end
end end
context 'when file is a pdf' do context 'the user wants children hidden' do
let(:file_name) { 'git-cheat-sheet.pdf' } let(:nesting_params) { { show_children: 'hidden' } }
it 'sets the content type to sets the content response headers' do it 'hides children' do
expect(response.headers['Content-Disposition']).to match(/^inline/) expect(assigns :show_children).to be false
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end end
end end
end end
end end
describe 'POST #preview_markdown' do include_examples 'sorting-and-nesting', ProjectWiki::CREATED_AT_ORDER, ProjectWiki::NESTING_FLAT
it 'renders json in a correct format' do include_examples 'sorting-and-nesting', ProjectWiki::TITLE_ORDER, ProjectWiki::NESTING_CLOSED
post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
expect(json_response.keys).to match_array(%w(body references))
end
end
describe 'GET #edit' do
subject { get(:edit, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }) }
context 'when page content encoding is invalid' do
it 'redirects to show' do
allow(controller).to receive(:valid_encoding?).and_return(false)
subject
expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first))
end
end
context 'when page content encoding is valid' do
render_views
it 'shows the edit page' do
subject
expect(response).to have_http_status(:ok)
expect(response.body).to include('Edit Page')
end
end
end
describe 'PATCH #update' do
let(:new_title) { 'New title' }
let(:new_content) { 'New content' }
subject do
patch(:update,
params: {
namespace_id: project.namespace,
project_id: project,
id: wiki_title,
wiki: { title: new_title, content: new_content }
})
end
context 'when page content encoding is invalid' do
it 'redirects to show' do
allow(controller).to receive(:valid_encoding?).and_return(false)
subject
expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first))
end
end
context 'when page content encoding is valid' do
render_views
it 'updates the page' do
subject
wiki_page = project_wiki.list_pages(load_content: true).first
expect(wiki_page.title).to eq new_title
expect(wiki_page.content).to eq new_content
end
end
end end
def create_page(name, content) def create_page(name, content)
......
...@@ -18,12 +18,12 @@ FactoryBot.define do ...@@ -18,12 +18,12 @@ FactoryBot.define do
association :wiki, factory: :project_wiki, strategy: :build association :wiki, factory: :project_wiki, strategy: :build
initialize_with { new(wiki, page, true) } initialize_with { new(wiki, page, true) }
before(:create) do |page, evaluator| before(:create) do |wiki_page, evaluator|
page.attributes = evaluator.attrs wiki_page.attributes = evaluator.attrs.with_indifferent_access
end end
to_create do |page| to_create do |wiki_page|
page.create wiki_page.create
end end
end end
end end
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
require 'spec_helper' require 'spec_helper'
describe 'Edit Project Settings' do describe 'Edit Project Settings' do
set(:project) { create(:project, :public, :repository) }
let(:member) { create(:user) } let(:member) { create(:user) }
let!(:project) { create(:project, :public, :repository) }
let!(:issue) { create(:issue, project: project) } let!(:issue) { create(:issue, project: project) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
...@@ -81,49 +82,52 @@ describe 'Edit Project Settings' do ...@@ -81,49 +82,52 @@ describe 'Edit Project Settings' do
end end
describe 'project features visibility pages' do describe 'project features visibility pages' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) } set(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:job) { create(:ci_build, pipeline: pipeline) } set(:job) { create(:ci_build, pipeline: pipeline) }
let(:tools) do where(:method_name, :build_url) do
{ [
builds: project_job_path(project, job), [:builds, -> { project_job_path(project, job) }],
issues: project_issues_path(project), [:issues, -> { project_issues_path(project) }],
wiki: project_wiki_path(project, :home), [:wiki, -> { project_wiki_path(project, :home) }],
snippets: project_snippets_path(project), [:snippets, -> { project_snippets_path(project) }],
merge_requests: project_merge_requests_path(project) [:merge_requests, -> { project_merge_requests_path(project) }]
} ]
end end
with_them do
let(:url) { build_url.call }
let(:attr_name) { "#{method_name}_access_level" }
context 'normal user' do context 'normal user' do
before do before do
project.team.truncate
sign_in(member) sign_in(member)
end end
it 'renders 200 if tool is enabled' do it 'renders 200 if tool is enabled' do
tools.each do |method_name, url| project.project_feature.update_attribute(attr_name, ProjectFeature::ENABLED)
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::ENABLED)
visit url visit url
expect(page.status_code).to eq(200) expect(page.status_code).to eq(200)
end end
end
it 'renders 404 if feature is disabled' do it 'renders 404 if feature is disabled' do
tools.each do |method_name, url| project.project_feature.update_attribute(attr_name, ProjectFeature::DISABLED)
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
visit url visit url
expect(page.status_code).to eq(404) expect(page.status_code).to eq(404)
end end
end
it 'renders 404 if feature is enabled only for team members' do it 'renders 404 if feature is enabled only for team members' do
project.team.truncate project.project_feature.update_attribute(attr_name, ProjectFeature::PRIVATE)
tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url visit url
expect(page.status_code).to eq(404) expect(page.status_code).to eq(404)
end end
end
it 'renders 200 if user is member of group' do it 'renders 200 if user is member of group' do
group = create(:group) group = create(:group)
...@@ -132,34 +136,34 @@ describe 'Edit Project Settings' do ...@@ -132,34 +136,34 @@ describe 'Edit Project Settings' do
group.add_owner(member) group.add_owner(member)
tools.each do |method_name, url| project.project_feature.update_attribute(attr_name, ProjectFeature::PRIVATE)
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url visit url
expect(page.status_code).to eq(200) expect(page.status_code).to eq(200)
end end
end end
end
context 'admin user' do context 'admin user' do
before do before do
non_member.update_attribute(:admin, true) non_member.update_attribute(:admin, true)
project.team.truncate
sign_in(non_member) sign_in(non_member)
end end
it 'renders 404 if feature is disabled' do it 'renders 404 if feature is disabled' do
tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED) project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
visit url visit url
expect(page.status_code).to eq(404) expect(page.status_code).to eq(404)
end end
end
it 'renders 200 if feature is enabled only for team members' do it 'renders 200 if feature is enabled only for team members' do
project.team.truncate
tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url visit url
expect(page.status_code).to eq(200) expect(page.status_code).to eq(200)
end end
end end
......
...@@ -4,164 +4,54 @@ require 'spec_helper' ...@@ -4,164 +4,54 @@ require 'spec_helper'
describe 'Projects > Wiki > User previews markdown changes', :js do describe 'Projects > Wiki > User previews markdown changes', :js do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } set(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) } let(:project_wiki) { ProjectWiki.new(project, user) }
let(:wiki_content) do
<<-HEREDOC
[regular link](regular)
[relative link 1](../relative)
[relative link 2](./relative)
[relative link 3](./e/f/relative)
[spaced link](title with spaces)
HEREDOC
end
before do before do
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
init_home!
end end
context "while creating a new wiki page" do def init_home!
context "when there are no spaces or hyphens in the page name" do create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' })
it "rewrites relative links as expected" do
create_wiki_page('a/b/c/d', content: wiki_content)
expect(page).to have_content("regular link")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
context "when there are spaces in the page name" do
it "rewrites relative links as expected" do
create_wiki_page('a page/b page/c page/d page', content: wiki_content)
expect(page).to have_content("regular link")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end end
end
context "when there are hyphens in the page name" do
it "rewrites relative links as expected" do
create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content)
expect(page).to have_content("regular link")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") def fill_in_content!
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>") page.within '.wiki-form' do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>") fill_in :wiki_page_content, with: wiki_content
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end end
end end
end
context "while editing a wiki page" do
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
create_wiki_page('a/b/c/d')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content def show_preview!
click_on "Preview" page.within '.wiki-form' do
click_on 'Preview'
expect(page).to have_content("regular link")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end end
end end
context "when there are spaces in the page name" do context 'when writing a new page' do
it "rewrites relative links as expected" do let(:new_wiki_path) { 'a/b/c/d' }
create_wiki_page('a page/b page/c page/d page') let(:wiki_content) { 'Some [awesome wiki](content)' }
click_link 'Edit'
fill_in :wiki_content, with: wiki_content it 'can show a preview of markdown content' do
click_on "Preview" visit project_wiki_pages_new_path(project, id: new_wiki_path)
fill_in_content!
show_preview!
expect(page).to have_content("regular link") expect(page).to have_link('awesome wiki')
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end end
end end
context "when there are hyphens in the page name" do context 'when editing an existing page' do
it "rewrites relative links as expected" do let(:wiki_content) { 'Some [bemusing](content)' }
create_wiki_page('a-page/b-page/c-page/d-page') let(:wiki_page) { create(:wiki_page, wiki: project_wiki) }
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
click_on "Preview"
expect(page).to have_content("regular link") it 'can show a preview of markdown content, when writing' do
visit project_wiki_edit_path(project, wiki_page)
fill_in_content!
show_preview!
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/regular\">regular link</a>") expect(page).to have_link('bemusing')
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end end
end end
context 'when rendering the preview' do
it 'renders content with CommonMark' do
create_wiki_page('a-page/b-page/c-page/common-mark')
click_link 'Edit'
fill_in :wiki_content, with: "1. one\n - sublist\n"
click_on "Preview"
# the above generates two separate lists (not embedded) in CommonMark
expect(page).to have_content("sublist")
expect(page).not_to have_xpath("//ol//li//ul")
end
end
end
it "does not linkify double brackets inside code blocks as expected" do
wiki_content = <<-HEREDOC
`[[do_not_linkify]]`
```
[[also_do_not_linkify]]
```
HEREDOC
create_wiki_page('linkify_test', wiki_content)
expect(page).to have_content("do_not_linkify")
expect(page.html).to include('[[do_not_linkify]]')
expect(page.html).to include('[[also_do_not_linkify]]')
end
private
def create_wiki_page(path, content = 'content')
visit project_wiki_path(project, wiki_page)
click_link 'New page'
fill_in :wiki_title, with: path
fill_in :wiki_content, with: content
click_button 'Create page'
end
end end
...@@ -6,6 +6,7 @@ describe 'User deletes wiki page', :js do ...@@ -6,6 +6,7 @@ describe 'User deletes wiki page', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
let(:project_wiki) { ProjectWiki.new(project, user) }
before do before do
sign_in(user) sign_in(user)
...@@ -18,5 +19,6 @@ describe 'User deletes wiki page', :js do ...@@ -18,5 +19,6 @@ describe 'User deletes wiki page', :js do
find('.modal-footer .btn-danger').click find('.modal-footer .btn-danger').click
expect(page).to have_content('Page was successfully deleted') expect(page).to have_content('Page was successfully deleted')
expect(project_wiki.find_page(wiki_page.slug)).to be nil
end end
end end
...@@ -10,6 +10,13 @@ describe 'User updates wiki page' do ...@@ -10,6 +10,13 @@ describe 'User updates wiki page' do
sign_in(user) sign_in(user)
end end
def create_page(attrs = {})
page.within('.wiki-form') do
attrs.each { |k, v| fill_in("wiki_page_#{k}".to_sym, with: v) }
click_on('Create page')
end
end
context 'when wiki is empty' do context 'when wiki is empty' do
before do before do
visit(project_wikis_path(project)) visit(project_wikis_path(project))
...@@ -28,12 +35,7 @@ describe 'User updates wiki page' do ...@@ -28,12 +35,7 @@ describe 'User updates wiki page' do
end end
it 'updates a page that has a path', :js do it 'updates a page that has a path', :js do
fill_in(:wiki_title, with: 'one/two/three-test') create_page(title: 'one/two/three-test', content: 'wiki content')
page.within '.wiki-form' do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
end
expect(current_path).to include('one/two/three-test') expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('three') expect(find('.wiki-pages')).to have_content('three')
...@@ -72,9 +74,9 @@ describe 'User updates wiki page' do ...@@ -72,9 +74,9 @@ describe 'User updates wiki page' do
it 'updates a page', :js do it 'updates a page', :js do
# Commit message field should have correct value. # Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki_page[message]', with: 'Update home')
fill_in(:wiki_content, with: 'My awesome wiki!') fill_in(:wiki_page_content, with: 'My awesome wiki!')
click_button('Save changes') click_button('Save changes')
expect(page).to have_content('Home') expect(page).to have_content('Home')
...@@ -83,31 +85,31 @@ describe 'User updates wiki page' do ...@@ -83,31 +85,31 @@ describe 'User updates wiki page' do
end end
it 'updates the commit message as the title is changed', :js do it 'updates the commit message as the title is changed', :js do
fill_in(:wiki_title, with: 'Wiki title') fill_in(:wiki_page_title, with: 'Wiki title')
expect(page).to have_field('wiki[message]', with: 'Update Wiki title') expect(page).to have_field('wiki_page[message]', with: 'Update Wiki title')
end end
it 'does not allow XSS', :js do it 'does not allow XSS', :js do
fill_in(:wiki_title, with: '<script>') fill_in(:wiki_page_title, with: '<script>')
expect(page).to have_field('wiki[message]', with: 'Update &lt;script&gt;') expect(page).to have_field('wiki_page[message]', with: 'Update &lt;script&gt;')
end end
it 'shows a validation error message' do it 'shows a validation error message' do
fill_in(:wiki_content, with: '') fill_in(:wiki_page_content, with: '')
click_button('Save changes') click_button('Save changes')
expect(page).to have_selector('.wiki-form') expect(page).to have_selector('.wiki-form')
expect(page).to have_content('Edit Page') expect(page).to have_content('Edit Page')
expect(page).to have_content('The form contains the following error:') expect(page).to have_content('The form contains the following error:')
expect(page).to have_content("Content can't be blank") expect(page).to have_content("Content can't be blank")
expect(find('textarea#wiki_content').value).to eq('') expect(find('textarea#wiki_page_content').value).to eq('')
end end
it 'shows the emoji autocompletion dropdown', :js do it 'shows the emoji autocompletion dropdown', :js do
find('#wiki_content').native.send_keys('') find('#wiki_page_content').native.send_keys('')
fill_in(:wiki_content, with: ':') fill_in(:wiki_page_content, with: ':')
expect(page).to have_selector('.atwho-view') expect(page).to have_selector('.atwho-view')
end end
...@@ -143,9 +145,9 @@ describe 'User updates wiki page' do ...@@ -143,9 +145,9 @@ describe 'User updates wiki page' do
it 'updates a page', :js do it 'updates a page', :js do
# Commit message field should have correct value. # Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki_page[message]', with: 'Update home')
fill_in(:wiki_content, with: 'My awesome wiki!') fill_in(:wiki_page_content, with: 'My awesome wiki!')
click_button('Save changes') click_button('Save changes')
...@@ -169,50 +171,43 @@ describe 'User updates wiki page' do ...@@ -169,50 +171,43 @@ describe 'User updates wiki page' do
visit(project_wiki_edit_path(project, wiki_page)) visit(project_wiki_edit_path(project, wiki_page))
end end
it 'moves the page to the root folder' do def edit_title!(title)
fill_in(:wiki_title, with: "/#{page_name}") fill_in(:wiki_page_title, with: title)
click_button('Save changes') click_button('Save changes')
end
it 'moves the page to the root folder' do
edit_title!("/#{page_name}")
expect(current_path).to eq(project_wiki_path(project, page_name)) expect(current_path).to eq(project_wiki_path(project, page_name))
end end
it 'moves the page to other dir' do it 'moves the page to other dir' do
new_page_dir = "foo1/bar1/#{page_name}" new_page_path = "baz/quux/#{page_name}"
edit_title!(new_page_path)
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes') expect(current_path).to eq(project_wiki_path(project, new_page_path))
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end end
it 'remains in the same place if title has not changed' do it 'remains in the same place if title has not changed' do
original_path = project_wiki_path(project, wiki_page) original_path = project_wiki_path(project, wiki_page)
edit_title!(page_name)
fill_in(:wiki_title, with: page_name)
click_button('Save changes')
expect(current_path).to eq(original_path) expect(current_path).to eq(original_path)
end end
it 'can be moved to a different dir with a different name' do it 'can be moved to a different dir with a different name' do
new_page_dir = "foo1/bar1/new_page_name" new_page_path = "quux/baz/new_page_name"
edit_title!(new_page_path)
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes') expect(current_path).to eq(project_wiki_path(project, new_page_path))
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end end
it 'can be renamed and moved to the root folder' do it 'can be renamed and moved to the root folder' do
new_name = 'new_page_name' new_name = 'new_page_name'
fill_in(:wiki_title, with: "/#{new_name}") edit_title!("/#{new_name}")
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_name)) expect(current_path).to eq(project_wiki_path(project, new_name))
end end
...@@ -220,9 +215,7 @@ describe 'User updates wiki page' do ...@@ -220,9 +215,7 @@ describe 'User updates wiki page' do
it 'squishes the title before creating the page' do it 'squishes the title before creating the page' do
new_page_dir = " foo1 / bar1 / #{page_name} " new_page_dir = " foo1 / bar1 / #{page_name} "
fill_in(:wiki_title, with: new_page_dir) edit_title!(new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}")) expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
end end
......
...@@ -8,10 +8,11 @@ describe 'User views a wiki page' do ...@@ -8,10 +8,11 @@ describe 'User views a wiki page' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' } let(:path) { 'image.png' }
let(:wiki_content) { "Look at this [image](#{path})\n\n ![alt text](#{path})" }
let(:wiki_page) do let(:wiki_page) do
create(:wiki_page, create(:wiki_page,
wiki: project.wiki, wiki: project.wiki,
attrs: { title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})" }) attrs: { title: 'home', content: wiki_content })
end end
before do before do
...@@ -19,17 +20,18 @@ describe 'User views a wiki page' do ...@@ -19,17 +20,18 @@ describe 'User views a wiki page' do
sign_in(user) sign_in(user)
end end
def create_page(attrs = {})
page.within('.wiki-form') do
attrs.each { |k, v| fill_in("wiki_page_#{k}".to_sym, with: v) }
click_on('Create page')
end
end
context 'when wiki is empty' do context 'when wiki is empty' do
before do before do
visit(project_wikis_path(project)) visit(project_wikis_path(project))
click_link "Create your first page" click_link "Create your first page"
create_page(title: 'one/two/three-test', content: 'wiki content')
fill_in(:wiki_title, with: 'one/two/three-test')
page.within('.wiki-form') do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
end
end end
it 'shows the history of a page that has a path', :js do it 'shows the history of a page that has a path', :js do
...@@ -83,24 +85,27 @@ describe 'User views a wiki page' do ...@@ -83,24 +85,27 @@ describe 'User views a wiki page' do
context 'shows a file stored in a page' do context 'shows a file stored in a page' do
let(:path) { upload_file_to_wiki(project, user, 'dk.png') } let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
let(:image_path) { project_wiki_path(project, path) }
it do it do
expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/#{path}']") expect(page).to have_xpath("//img[@data-src='#{image_path}']")
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}") expect(page).to have_link('image', href: "#{image_path}")
click_on('image') click_on('image')
expect(current_path).to match("wikis/#{path}") expect(current_path).to match(path)
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
end end
end end
it 'shows the creation page if file does not exist' do it 'shows the creation page if file does not exist' do
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}") href = project_wiki_path(project, path)
expect(page).to have_link('image', href: href)
click_on('image') click_on('image')
expect(current_path).to match("wikis/#{path}") expect(current_path).to match(href)
expect(page).to have_content('Create New Page') expect(page).to have_content('Create New Page')
end end
end end
......
...@@ -7,6 +7,7 @@ describe 'User views wiki pages' do ...@@ -7,6 +7,7 @@ describe 'User views wiki pages' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:project_wiki) { ProjectWiki.new(project, user) }
let!(:wiki_page1) do let!(:wiki_page1) do
create(:wiki_page, wiki: project.wiki, attrs: { title: '3 home', content: '3' }) create(:wiki_page, wiki: project.wiki, attrs: { title: '3 home', content: '3' })
...@@ -17,73 +18,182 @@ describe 'User views wiki pages' do ...@@ -17,73 +18,182 @@ describe 'User views wiki pages' do
let!(:wiki_page3) do let!(:wiki_page3) do
create(:wiki_page, wiki: project.wiki, attrs: { title: '2 home', content: '2' }) create(:wiki_page, wiki: project.wiki, attrs: { title: '2 home', content: '2' })
end end
let!(:wiki_page4) do
create(:wiki_page, wiki: project.wiki, attrs: { title: 'sub-folder/0', content: 'a' })
end
let!(:wiki_page5) do
create(:wiki_page, wiki: project.wiki, attrs: { title: 'sub-folder/b', content: 'b' })
end
let(:page_link_selector) { 'a' }
let(:pages) do let(:pages) do
page.find('.wiki-pages-list').all('li').map { |li| li.find('a') } page.all(".wiki-pages-list li #{page_link_selector}")
end end
let(:wikis_allow_change_nesting) { false }
before do before do
stub_feature_flags(wikis_allow_change_nesting: wikis_allow_change_nesting)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
visit(project_wikis_pages_path(project)) visit(project_wikis_pages_path(project))
end end
context 'ordered by title' do def sort_desc!
let(:pages_ordered_by_title) { [wiki_page2, wiki_page3, wiki_page1] } page.within('.wiki-sort-dropdown') do
page.find('.qa-reverse-sort').click
end
end
context 'asc' do def sort_by_created_at!
it 'pages are displayed in direct order' do page.within('.wiki-sort-dropdown') do
pages.each.with_index do |page_title, index| click_button('Title')
expect(page_title.text).to eq(pages_ordered_by_title[index].title) click_link('Created date')
end
end end
shared_examples 'correctly_sorted_pages' do
it 'has pages displayed in correct order' do
displayed_texts = pages.map(&:text)
expect(displayed_texts).to eq expected_sequence.map(&:title)
end end
end end
context 'ordered by title' do
let(:sub_folder) { project_wiki.find_dir('sub-folder') }
context 'default display settings' do
context 'asc' do
let(:expected_sequence) { [wiki_page2, wiki_page3, wiki_page1, sub_folder] }
it_behaves_like 'correctly_sorted_pages'
end
context 'desc' do context 'desc' do
before do before do
page.within('.wiki-sort-dropdown') do sort_desc!
page.find('.rspec-reverse-sort').click end
let(:expected_sequence) { [sub_folder, wiki_page1, wiki_page3, wiki_page2] }
it_behaves_like 'correctly_sorted_pages'
end end
end end
it 'pages are displayed in reversed order' do context 'changing nesting is disabled' do
pages.reverse_each.with_index do |page_title, index| let(:wikis_allow_change_nesting) { false }
expect(page_title.text).to eq(pages_ordered_by_title[index].title)
it 'does not display a nesting controller' do
expect(page).not_to have_css('.wiki-nesting-dropdown')
end
end end
context 'changing nesting is enabled' do
let(:wikis_allow_change_nesting) { true }
it 'displays a nesting controller' do
expect(page).to have_css('.wiki-nesting-dropdown')
end end
context 'tree' do
before do
page.within('.wiki-nesting-dropdown') do
click_link 'Show folder contents'
end end
end end
context 'ordered by created_at' do context 'asc' do
let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] } let(:expected_sequence) { [wiki_page2, wiki_page3, wiki_page1, sub_folder, wiki_page4, wiki_page5] }
it_behaves_like 'correctly_sorted_pages'
end
context 'desc' do
before do before do
page.within('.wiki-sort-dropdown') do sort_desc!
click_button('Title') end
click_link('Created date')
let(:expected_sequence) { [sub_folder, wiki_page5, wiki_page4, wiki_page1, wiki_page3, wiki_page2] }
it_behaves_like 'correctly_sorted_pages'
end
end
context 'nested' do
before do
page.within('.wiki-nesting-dropdown') do
click_link 'Hide folder contents'
end end
end end
context 'asc' do context 'asc' do
it 'pages are displayed in direct order' do let(:expected_sequence) { [wiki_page2, wiki_page3, wiki_page1, sub_folder] }
pages.each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title) it_behaves_like 'correctly_sorted_pages'
end
context 'desc' do
before do
sort_desc!
end
let(:expected_sequence) { [sub_folder, wiki_page1, wiki_page3, wiki_page2] }
it_behaves_like 'correctly_sorted_pages'
end
end
context 'flat' do
before do
page.within('.wiki-nesting-dropdown') do
click_link 'Show files separately'
end end
end end
let(:page_link_selector) { 'a.wiki-page-title' }
context 'asc' do
let(:expected_sequence) { [wiki_page2, wiki_page3, wiki_page1, wiki_page4, wiki_page5] }
it_behaves_like 'correctly_sorted_pages'
end end
context 'desc' do context 'desc' do
before do before do
page.within('.wiki-sort-dropdown') do sort_desc!
page.find('.rspec-reverse-sort').click end
let(:expected_sequence) { [wiki_page5, wiki_page4, wiki_page1, wiki_page3, wiki_page2] }
it_behaves_like 'correctly_sorted_pages'
end
end end
end end
end
context 'ordered by created_at' do
let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3, wiki_page4, wiki_page5] }
it 'pages are displayed in reversed order' do before do
pages.reverse_each.with_index do |page_title, index| sort_by_created_at!
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end end
let(:page_link_selector) { 'a.wiki-page-title' }
context 'asc' do
let(:expected_sequence) { [wiki_page1, wiki_page2, wiki_page3, wiki_page4, wiki_page5] }
it_behaves_like 'correctly_sorted_pages'
end end
context 'desc' do
before do
sort_desc!
end
let(:expected_sequence) { [wiki_page5, wiki_page4, wiki_page3, wiki_page2, wiki_page1] }
it_behaves_like 'correctly_sorted_pages'
end end
end end
end end
...@@ -3,27 +3,27 @@ import { setHTMLFixture } from './helpers/fixtures'; ...@@ -3,27 +3,27 @@ import { setHTMLFixture } from './helpers/fixtures';
describe('Wikis', () => { describe('Wikis', () => {
describe('setting the commit message when the title changes', () => { describe('setting the commit message when the title changes', () => {
const editFormHtmlFixture = args => `<form class="wiki-form ${
args.newPage ? 'js-new-wiki-page' : ''
}">
<input type="text" id="wiki_title" value="My title" />
<input type="text" id="wiki_message" />
</form>`;
let wikis; let wikis;
let titleInput; let titleInput;
let messageInput; let messageInput;
const CREATE = true;
const UPDATE = false;
describe('when the wiki page is being created', () => { const editFormHtmlFixture = newPage =>
const formHtmlFixture = editFormHtmlFixture({ newPage: true }); `<form class="wiki-form ${newPage ? 'js-new-wiki-page' : ''}">
<input type="text" id="wiki_page_title" value="My title" />
beforeEach(() => { <input type="text" id="wiki_page_message" />
setHTMLFixture(formHtmlFixture); </form>`;
titleInput = document.getElementById('wiki_title'); const init = newPage => {
messageInput = document.getElementById('wiki_message'); setHTMLFixture(editFormHtmlFixture(newPage));
titleInput = document.getElementById('wiki_page_title');
messageInput = document.getElementById('wiki_page_message');
wikis = new Wikis(); wikis = new Wikis();
}); };
describe('when the wiki page is being created', () => {
beforeEach(() => init(CREATE));
it('binds an event listener to the title input', () => { it('binds an event listener to the title input', () => {
wikis.handleWikiTitleChange = jest.fn(); wikis.handleWikiTitleChange = jest.fn();
...@@ -51,15 +51,7 @@ describe('Wikis', () => { ...@@ -51,15 +51,7 @@ describe('Wikis', () => {
}); });
describe('when the wiki page is being updated', () => { describe('when the wiki page is being updated', () => {
const formHtmlFixture = editFormHtmlFixture({ newPage: false }); beforeEach(() => init(UPDATE));
beforeEach(() => {
setHTMLFixture(formHtmlFixture);
titleInput = document.getElementById('wiki_title');
messageInput = document.getElementById('wiki_message');
wikis = new Wikis();
});
it('sets the commit message when title changes, prefixing with "Update"', () => { it('sets the commit message when title changes, prefixing with "Update"', () => {
titleInput.value = 'My title'; titleInput.value = 'My title';
......
...@@ -23,8 +23,13 @@ describe WikiHelper do ...@@ -23,8 +23,13 @@ describe WikiHelper do
describe '#wiki_sort_controls' do describe '#wiki_sort_controls' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) } let(:classes) { described_class::WIKI_SORT_CSS_CLASSES }
let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
subject(:wiki_link) do
helper.wiki_sort_controls(sort: sort, direction: direction) do |opts|
project_wikis_pages_path(project, opts)
end
end
def expected_link(sort, direction, icon_class) def expected_link(sort, direction, icon_class)
path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}" path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
...@@ -62,6 +67,18 @@ describe WikiHelper do ...@@ -62,6 +67,18 @@ describe WikiHelper do
end end
end end
describe '#wiki_show_children_icon' do
ProjectWiki::NESTINGS.each do |nesting|
context "When the nesting parameter is `#{nesting}`" do
let(:element) { helper.wiki_show_children_icon(nesting) }
it 'produces something that contains an SVG' do
expect(element).to match(/svg/)
end
end
end
end
describe '#wiki_sort_title' do describe '#wiki_sort_title' do
it 'returns a title corresponding to a key' 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('created_at')).to eq('Created date')
......
...@@ -11,6 +11,10 @@ describe Banzai::Filter::WikiLinkFilter do ...@@ -11,6 +11,10 @@ describe Banzai::Filter::WikiLinkFilter do
let(:wiki) { ProjectWiki.new(project, user) } let(:wiki) { ProjectWiki.new(project, user) }
let(:repository_upload_folder) { Wikis::CreateAttachmentService::ATTACHMENT_PATH } let(:repository_upload_folder) { Wikis::CreateAttachmentService::ATTACHMENT_PATH }
def upload_href(file_name)
::File.join(wiki.wiki_page_path, repository_upload_folder, file_name)
end
it "doesn't rewrite absolute links" do it "doesn't rewrite absolute links" do
filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0] filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0]
...@@ -28,12 +32,12 @@ describe Banzai::Filter::WikiLinkFilter do ...@@ -28,12 +32,12 @@ describe Banzai::Filter::WikiLinkFilter do
it 'rewrites links' do it 'rewrites links' do
filtered_link = filter("<a href='#{repository_upload_folder}/a.test'>Link</a>", project_wiki: wiki).children[0] filtered_link = filter("<a href='#{repository_upload_folder}/a.test'>Link</a>", project_wiki: wiki).children[0]
expect(filtered_link.attribute('href').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.test") expect(filtered_link.attribute('href').value).to eq(upload_href "a.test")
end end
end end
context 'with "img" html tag' do context 'with "img" html tag' do
let(:path) { "#{wiki.wiki_base_path}/#{repository_upload_folder}/a.jpg" } let(:path) { upload_href "a.jpg" }
context 'inside an "a" html tag' do context 'inside an "a" html tag' do
it 'rewrites links' do it 'rewrites links' do
...@@ -57,7 +61,7 @@ describe Banzai::Filter::WikiLinkFilter do ...@@ -57,7 +61,7 @@ describe Banzai::Filter::WikiLinkFilter do
it 'rewrites links' do it 'rewrites links' do
filtered_link = filter("<video src='#{repository_upload_folder}/a.mp4'></video>", project_wiki: wiki).children[0] filtered_link = filter("<video src='#{repository_upload_folder}/a.mp4'></video>", project_wiki: wiki).children[0]
expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.mp4") expect(filtered_link.attribute('src').value).to eq(upload_href "a.mp4")
end end
end end
...@@ -65,7 +69,8 @@ describe Banzai::Filter::WikiLinkFilter do ...@@ -65,7 +69,8 @@ describe Banzai::Filter::WikiLinkFilter do
it 'rewrites links' do it 'rewrites links' do
filtered_link = filter("<audio src='#{repository_upload_folder}/a.wav'></audio>", project_wiki: wiki).children[0] filtered_link = filter("<audio src='#{repository_upload_folder}/a.wav'></audio>", project_wiki: wiki).children[0]
expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.wav") # expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.wav")
expect(filtered_link.attribute('src').value).to eq(upload_href "a.wav")
end end
end end
end end
......
...@@ -150,7 +150,7 @@ describe Gitlab::UrlBuilder do ...@@ -150,7 +150,7 @@ describe Gitlab::UrlBuilder do
wiki_page = build(:wiki_page) wiki_page = build(:wiki_page)
url = described_class.build(wiki_page) url = described_class.build(wiki_page)
expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_base_path}/#{wiki_page.slug}" expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_page_path}/#{wiki_page.slug}"
end end
end end
end end
......
...@@ -252,4 +252,41 @@ describe Gitlab::Utils do ...@@ -252,4 +252,41 @@ describe Gitlab::Utils do
expect(described_class.string_to_ip_object('1:0:0:0:0:0:0:0/124')).to eq(IPAddr.new('1:0:0:0:0:0:0:0/124')) expect(described_class.string_to_ip_object('1:0:0:0:0:0:0:0/124')).to eq(IPAddr.new('1:0:0:0:0:0:0:0/124'))
end end
end end
describe '.allow_hash_values' do
it 'removes keys that do not pass the inclusion filters' do
symbols = %i[x y z]
ints = (0..100)
strings = %w[foo bar baz].to_set
hash = {
a: :x,
b: 100,
c: 'foo',
d: :irrelevant,
aa: :w,
bb: 200,
cc: 'food',
dd: :totally_irrelevant
}
allowed = {
a: symbols,
b: ints,
c: strings,
aa: symbols,
bb: ints,
cc: strings
}
described_class.allow_hash_values(hash, allowed)
expect(hash).to eq({
a: :x,
b: 100,
c: 'foo',
d: :irrelevant,
dd: :totally_irrelevant
})
end
end
end end
...@@ -28,7 +28,9 @@ describe ProjectWiki do ...@@ -28,7 +28,9 @@ describe ProjectWiki do
describe '#web_url' do describe '#web_url' do
it 'returns the full web URL to the wiki' do it 'returns the full web URL to the wiki' do
expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.full_path}/wikis/home") home_url = Gitlab::Routing.url_helpers.project_wiki_url(project, :home)
expect(subject.web_url).to eq(home_url)
end end
end end
...@@ -71,9 +73,23 @@ describe ProjectWiki do ...@@ -71,9 +73,23 @@ describe ProjectWiki do
describe "#wiki_base_path" do describe "#wiki_base_path" do
it "returns the wiki base path" do it "returns the wiki base path" do
wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.full_path}/wikis" wiki_path = Gitlab::Routing.url_helpers.project_wikis_path(project)
expect(subject.wiki_base_path).to eq(wiki_path)
end
end
expect(subject.wiki_base_path).to eq(wiki_base_path) describe "#wiki_page_path" do
let(:page) { create(:wiki_page, wiki: project_wiki) }
describe 'suffixed with /:page_slug' do
subject { "#{project_wiki.wiki_page_path}/#{page.slug}" }
it "equals the project_wiki_path" do
path = Gitlab::Routing.url_helpers.project_wiki_path(project, page)
expect(subject).to eq(path)
end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
require 'set'
RSpec.describe WikiDirectory do RSpec.describe WikiDirectory do
include GitHelpers
let(:project) { create(:project, :wiki_repo) }
let(:user) { project.owner }
let(:wiki) { ProjectWiki.new(project, user) }
describe 'validations' do describe 'validations' do
subject { build(:wiki_directory) } subject { build(:wiki_directory) }
it { is_expected.to validate_presence_of(:slug) } it { is_expected.to validate_presence_of(:slug) }
end end
describe '.group_by_directory' do
context 'when there are no pages' do
it 'returns an empty array' do
expect(described_class.group_by_directory(nil)).to eq([])
expect(described_class.group_by_directory([])).to eq([])
end
end
context 'when there are pages' do
before do
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_2', 'page with dir name')
create_page('dir_2/page_5', 'content')
create_page('page_6', 'content')
create_page('dir_2/page_4', 'content')
end
let(:page_1) { wiki.find_page('page_1') }
let(:page_6) { wiki.find_page('page_6') }
let(:page_dir_2) { wiki.find_page('dir_2') }
let(:dir_1) do
described_class.new('dir_1', [wiki.find_page('dir_1/page_2')])
end
let(:dir_1_1) do
described_class.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
end
let(:dir_2) do
pages = [wiki.find_page('dir_2/page_5'),
wiki.find_page('dir_2/page_4')]
described_class.new('dir_2', pages)
end
context "#list_pages" do
shared_examples "a correct grouping" do
let(:grouped_slugs) { grouped_entries.map(&method(:slugs)) }
let(:expected_slugs) { expected_grouped_entries.map(&method(:slugs)).map(&method(:match_array)) }
it 'returns an array with pages and directories' do
expect(grouped_slugs).to match_array(expected_slugs)
end
end
context 'sort by title' do
let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) }
let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] }
it_behaves_like "a correct grouping"
end
context 'sort by created_at' do
let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages(sort: 'created_at')) }
let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] }
it_behaves_like "a correct grouping"
end
it 'returns an array with retained order with directories at the top' do
expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
grouped_entries = described_class.group_by_directory(wiki.list_pages)
actual_order = grouped_entries.flat_map(&method(:slugs))
expect(actual_order).to eq(expected_order)
end
end
end
end
describe '#initialize' do describe '#initialize' do
context 'when there are pages' do context 'when there are pages' do
let(:pages) { [build(:wiki_page)] } let(:pages) { [build(:wiki_page)] }
...@@ -40,7 +120,112 @@ RSpec.describe WikiDirectory do ...@@ -40,7 +120,112 @@ RSpec.describe WikiDirectory do
it 'returns the relative path to the partial to be used' do it 'returns the relative path to the partial to be used' do
directory = build(:wiki_directory) directory = build(:wiki_directory)
expect(directory.to_partial_path).to eq('projects/wikis/wiki_directory') expect(directory.to_partial_path).to eq('projects/wiki_directories/wiki_directory')
end
end
describe 'attributes' do
def page_path(index)
"dir-path/page-#{index}"
end
let(:page_paths) { (1..3).map { |n| page_path(n) } }
let(:pages) do
page_paths.map { |p| wiki.find_page(p) }
end end
subject { described_class.new('dir-path', pages) }
context 'there are no pages' do
let(:pages) { [] }
it { is_expected.to have_attributes(page_count: 0, last_version: be_nil) }
end
context 'there is one page' do
before do
create_page("dir-path/singleton", "Just this page")
end
let(:the_page) { wiki.find_page("dir-path/singleton") }
let(:pages) { [the_page] }
it { is_expected.to have_attributes(page_count: 1, last_version: the_page.last_version) }
end
context 'there are a few pages, each with a single version' do
before do
page_paths.each_with_index do |path, n|
Timecop.freeze(Time.local(1990) + n.minutes) do
create_page(path, "this is page #{n}")
end
end
end
let(:expected_last_version) { pages.last.last_version }
it { is_expected.to have_attributes(page_count: 3, last_version: expected_last_version) }
end
context 'there are a few pages, each with a few versions' do
before do
page_paths.each_with_index do |path, n|
t = Time.local(1990) + n.minutes
Timecop.freeze(t) do
create_page(path, "This is page #{n}")
(2..3).each do |v|
Timecop.freeze(t + v.seconds) do
update_page(path, "Now at version #{v}")
end
end
end
end
end
it { is_expected.to have_attributes(page_count: 3, last_version: pages.last.last_version) }
end
end
private
def create_page(name, content)
wiki.wiki.write_page(name, :markdown, content, commit_details)
set_time(name)
end
def update_page(name, content)
wiki.wiki.update_page(name, name, :markdown, content, update_commit_details)
set_time(name)
end
def set_time(name)
return unless Timecop.frozen?
new_date = Time.now
page = wiki.find_page(name).page
commit = page.version.commit
repo = commit.instance_variable_get(:@repository)
rug_commit = rugged_repo_at_path(repo.relative_path).lookup(commit.id)
rug_commit.amend(
message: rug_commit.message,
tree: rug_commit.tree,
author: rug_commit.author.merge(time: new_date),
committer: rug_commit.committer.merge(time: new_date),
update_ref: 'HEAD'
)
end
def commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
end
def update_commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test update")
end
def slugs(thing)
Array.wrap(thing.respond_to?(:pages) ? thing.pages.map(&:slug) : thing.slug)
end end
end end
...@@ -9,87 +9,6 @@ describe WikiPage do ...@@ -9,87 +9,6 @@ describe WikiPage do
subject { described_class.new(wiki) } subject { described_class.new(wiki) }
describe '.group_by_directory' do
context 'when there are no pages' do
it 'returns an empty array' do
expect(described_class.group_by_directory(nil)).to eq([])
expect(described_class.group_by_directory([])).to eq([])
end
end
context 'when there are pages' do
before do
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_2', 'page with dir name')
create_page('dir_2/page_5', 'content')
create_page('page_6', 'content')
create_page('dir_2/page_4', 'content')
end
let(:page_1) { wiki.find_page('page_1') }
let(:page_6) { wiki.find_page('page_6') }
let(:page_dir_2) { wiki.find_page('dir_2') }
let(:dir_1) do
WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
end
let(:dir_1_1) do
WikiDirectory.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
end
let(:dir_2) do
pages = [wiki.find_page('dir_2/page_5'),
wiki.find_page('dir_2/page_4')]
WikiDirectory.new('dir_2', pages)
end
context "#list_pages" do
context 'sort by title' do
let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) }
let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] }
it 'returns an array with pages and directories' do
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)
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.list_pages(sort: 'created_at')) }
let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] }
it 'returns an array with pages and directories' do
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)
expect(slugs).to match_array(expected_slugs)
end
end
end
it 'returns an array with retained order with directories at the top' do
expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
grouped_entries = described_class.group_by_directory(wiki.list_pages)
actual_order =
grouped_entries.flat_map do |page_or_dir|
get_slugs(page_or_dir)
end
expect(actual_order).to eq(expected_order)
end
end
end
end
describe '.unhyphenize' do describe '.unhyphenize' do
it 'removes hyphens from a name' do it 'removes hyphens from a name' do
name = 'a-name--with-hyphens' name = 'a-name--with-hyphens'
...@@ -505,7 +424,7 @@ describe WikiPage do ...@@ -505,7 +424,7 @@ describe WikiPage do
it 'returns the relative path to the partial to be used' do it 'returns the relative path to the partial to be used' do
page = build(:wiki_page) page = build(:wiki_page)
expect(page.to_partial_path).to eq('projects/wikis/wiki_page') expect(page.to_partial_path).to eq('projects/wiki_pages/wiki_page')
end end
end end
...@@ -585,12 +504,4 @@ describe WikiPage do ...@@ -585,12 +504,4 @@ describe WikiPage do
page = wiki.wiki.page(title: title, dir: dir) page = wiki.wiki.page(title: title, dir: dir)
wiki.delete_page(page, "test commit") wiki.delete_page(page, "test commit")
end end
def get_slugs(page_or_dir)
if page_or_dir.is_a? WikiPage
[page_or_dir.slug]
else
page_or_dir.pages.present? ? page_or_dir.pages.map(&:slug) : []
end
end
end end
...@@ -3,9 +3,12 @@ require 'spec_helper' ...@@ -3,9 +3,12 @@ require 'spec_helper'
describe 'project routing' do describe 'project routing' do
before do before do
allow(Project).to receive(:find_by_full_path).and_return(false) allow(Project).to receive(:find_by_full_path).and_return(false)
allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(true) allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(project)
end end
set(:namespace) { create(:namespace, name: 'gitlab') }
set(:project) { create(:project, namespace: namespace, name: 'gitlabhq') }
# Shared examples for a resource inside a Project # Shared examples for a resource inside a Project
# #
# By default it tests all the default REST actions: index, create, new, edit, # By default it tests all the default REST actions: index, create, new, edit,
...@@ -145,24 +148,39 @@ describe 'project routing' do ...@@ -145,24 +148,39 @@ describe 'project routing' do
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/autocomplete_sources/labels", "/gitlab/gitlabhq/-/autocomplete_sources/labels" it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/autocomplete_sources/labels", "/gitlab/gitlabhq/-/autocomplete_sources/labels"
end end
# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages # GET /:project_id/wikis/pages(.:format) projects/wikis#pages
# history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history # GET /:project_id/-/wiki_pages/:id/history(.:format) projects/wiki_pages#history
# project_wikis POST /:project_id/wikis(.:format) projects/wikis#create # POST /:project_id/-/wiki_pages(.:format) projects/wiki_pages#create
# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit # GET /:project_id/-/wiki_pages/:id/edit(.:format) projects/wiki_pages#edit
# project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show # GET /:project_id/-/wiki_pages/:id(.:format) projects/wiki_pages#show
# DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy # DELETE /:project_id/-/wiki_pages/:id(.:format) projects/wiki_pages#destroy
describe Projects::WikisController, 'routing' do describe Projects::WikisController, 'routing' do
it 'to #pages' do let(:wiki) { ProjectWiki.new(project, project.owner) }
expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq') let(:wiki_page) { create(:wiki_page, wiki: wiki) }
it '#pages' do
expect(get('/gitlab/gitlabhq/wikis/pages'))
.to route_to('projects/wikis#pages',
namespace_id: 'gitlab',
project_id: 'gitlabhq')
end end
it 'to #history' do describe '#history' do
expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') let(:history_path) { project_wiki_history_path(project, wiki_page) }
it 'routes to history' do
expect(get(history_path))
.to route_to('projects/wiki_pages#history',
namespace_id: namespace.path,
project_id: project.name,
id: wiki_page.slug)
end
end end
it_behaves_like 'RESTful project resources' do it_behaves_like 'RESTful project resources' do
let(:actions) { [:create, :edit, :show, :destroy] } let(:actions) { [:create, :edit, :show, :destroy] }
let(:controller) { 'wikis' } let(:controller) { 'wiki_pages' }
let(:controller_path) { '-/wiki_pages' }
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
# We build URIs to wiki pages manually in various places (most notably
# in markdown generation). To ensure these do not get out of sync, these
# tests verify that our path generation assumptions are sound.
describe 'Wiki path generation assumptions' do
set(:project) { create(:project, :public, :repository) }
let(:project_wiki) { ProjectWiki.new(project, project.owner) }
let(:some_page_name) { 'some-wiki-page' }
let(:wiki_page) do
create(:wiki_page, wiki: project_wiki, attrs: { title: some_page_name })
end
describe 'WikiProject#wiki_page_path', 'routing' do
it 'is consistent with routing to wiki#show' do
uri = URI.parse(project_wiki.wiki_page_path)
path = ::File.join(uri.path, some_page_name)
expect(get('/' + path)).to route_to('projects/wiki_pages#show',
id: some_page_name,
namespace_id: project.namespace.to_param,
project_id: project.to_param)
end
end
describe 'project_wiki_path', 'routing' do
describe 'GET' do
it 'routes to the :show action' do
path = project_wiki_path(project, wiki_page)
expect(get('/' + path)).to route_to('projects/wiki_pages#show',
id: wiki_page.slug,
namespace_id: project.namespace.to_param,
project_id: project.to_param)
end
end
end
describe 'project_wiki_pages_new_path', 'routing' do
describe 'GET' do
it 'routes to the :new action' do
path = project_wiki_pages_new_path(project)
expect(get('/' + path)).to route_to('projects/wiki_pages#new',
namespace_id: project.namespace.to_param,
project_id: project.to_param)
end
end
end
# Early versions of the wiki paths routed all wiki pages at
# /wikis/:id - this test exists to guarantee that we support
# old URIs that may be out there, saved in bookmarks, on other wikis, etc.
describe 'legacy route support', type: 'request' do
let(:path) { ::File.join(project_wikis_path(project), some_page_name) }
before do
get(path)
end
it 'routes to new wiki paths' do
dest = project_wiki_path(project, wiki_page)
expect(response).to redirect_to(dest)
end
context 'the page is nested in a directory' do
let(:some_page_name) { 'some-dir/some-deep-dir/some-page' }
let(:path) { ::File.join(project_wikis_path(project), some_page_name) }
it 'still routes correctly' do
dest = project_wiki_path(project, wiki_page)
expect(response).to redirect_to(dest)
end
end
context 'the user requested the old history path' do
let(:some_page_name) { 'some-dir/some-deep-dir/some-page' }
let(:path) { ::File.join(project_wikis_path(project), some_page_name, 'history') }
it 'redirects to the new history path' do
dest = project_wiki_history_path(project, wiki_page)
expect(response).to redirect_to(dest)
end
end
context 'the user requested the old edit path' do
let(:some_page_name) { 'some-dir/some-deep-dir/some-page' }
let(:path) { ::File.join(project_wikis_path(project), some_page_name, 'edit') }
it 'redirects to the new history path' do
dest = project_wiki_edit_path(project, wiki_page)
expect(response).to redirect_to(dest)
end
end
end
end
# frozen_string_literal: true
def forbid_controller_ability!(ability)
allow(controller).to receive(:can?).and_call_original
allow(controller).to receive(:can?).with(anything, ability, any_args).and_return(false)
end
...@@ -46,4 +46,14 @@ module CapybaraHelpers ...@@ -46,4 +46,14 @@ module CapybaraHelpers
def javascript_test? def javascript_test?
Capybara.current_driver == Capybara.javascript_driver Capybara.current_driver == Capybara.javascript_driver
end end
def scroll_to(element)
raise 'JS not available' unless javascript_test?
script = <<-JS
arguments[0].scrollIntoView(true);
JS
page.driver.browser.execute_script(script, element.native)
end
end end
...@@ -14,6 +14,8 @@ module DropzoneHelper ...@@ -14,6 +14,8 @@ module DropzoneHelper
# If it's 'false', then the helper will NOT wait for backend response # If it's 'false', then the helper will NOT wait for backend response
# It lets to test behaviors while AJAX is processing. # It lets to test behaviors while AJAX is processing.
def dropzone_file(files, max_file_size = 0, wait_for_queuecomplete = true) def dropzone_file(files, max_file_size = 0, wait_for_queuecomplete = true)
# Assert that there is a dropzone to use (waiting until it is ready)
expect(page).to have_css('.div-dropzone')
# Generate a fake file input that Capybara can attach to # Generate a fake file input that Capybara can attach to
page.execute_script <<-JS.strip_heredoc page.execute_script <<-JS.strip_heredoc
$('#fakeFileInput').remove(); $('#fakeFileInput').remove();
......
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
module GitHelpers module GitHelpers
def rugged_repo(repository) def rugged_repo(repository)
path = File.join(TestEnv.repos_path, repository.disk_path + '.git') rugged_repo_at_path(repository.disk_path + '.git')
end
def rugged_repo_at_path(relative_path)
path = File.join(TestEnv.repos_path, relative_path)
Rugged::Repository.new(path) Rugged::Repository.new(path)
end end
end end
...@@ -12,4 +12,10 @@ module WikiHelpers ...@@ -12,4 +12,10 @@ module WikiHelpers
::Wikis::CreateAttachmentService.new(project, user, opts) ::Wikis::CreateAttachmentService.new(project, user, opts)
.execute[:result][:file_path] .execute[:result][:file_path]
end end
# Generate the form field name for a given attribute of an object.
# This is rather general, but is currently only used in the wiki featur tests.
def form_field_name(obj, attr_name)
"#{ActiveModel::Naming.param_key(obj)}[#{attr_name}]"
end
end end
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"| RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual| match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}") # anchors may be invisible
node = find("#{parent} h#{level} a#user-content-#{id}", visible: false)
expect(node[:href]).to end_with("##{id}") expect(node[:href]).to end_with("##{id}")
......
...@@ -42,7 +42,7 @@ shared_examples 'wiki file attachments' do ...@@ -42,7 +42,7 @@ shared_examples 'wiki file attachments' do
end end
end end
context 'uploading is complete', :quarantine do context 'uploading is complete' do
it 'shows "Attach a file" button on uploading complete' do it 'shows "Attach a file" button on uploading complete' do
attach_with_dropzone attach_with_dropzone
wait_for_requests wait_for_requests
...@@ -52,11 +52,11 @@ shared_examples 'wiki file attachments' do ...@@ -52,11 +52,11 @@ shared_examples 'wiki file attachments' do
end end
it 'the markdown link is added to the page' do it 'the markdown link is added to the page' do
fill_in(:wiki_content, with: '') fill_in(:wiki_page_content, with: '')
attach_with_dropzone(true) attach_with_dropzone(true)
wait_for_requests wait_for_requests
expect(page.find('#wiki_content').value) expect(page.find('#wiki_page_content').value)
.to match(%r{\!\[dk\]\(uploads/\h{32}/dk\.png\)$}) .to match(%r{\!\[dk\]\(uploads/\h{32}/dk\.png\)$})
end end
...@@ -70,7 +70,7 @@ shared_examples 'wiki file attachments' do ...@@ -70,7 +70,7 @@ shared_examples 'wiki file attachments' do
img_link = page.find('a.no-attachment-icon img')['src'] img_link = page.find('a.no-attachment-icon img')['src']
expect(link).to eq img_link expect(link).to eq img_link
expect(URI.parse(link).path).to eq File.join(wiki.wiki_base_path, file_path) expect(URI.parse(link).path).to eq File.join(wiki.wiki_page_path, file_path)
end end
it 'the file has been added to the wiki repository' do it 'the file has been added to the wiki repository' do
......
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