Commit 4618a4ff authored by Alex Kalderimis's avatar Alex Kalderimis Committed by Nick Thomas

Catch git timeouts during wiki actions

This adds tests for all the actions that we expect to catch gitaly
timeouts in, and special handling of failures to load the side-bar.
parent 2baf662c
...@@ -8,6 +8,8 @@ module WikiActions ...@@ -8,6 +8,8 @@ module WikiActions
include RedisTracking include RedisTracking
extend ActiveSupport::Concern extend ActiveSupport::Concern
RESCUE_GIT_TIMEOUTS_IN = %w[show edit history diff pages].freeze
included do included do
before_action { respond_to :html } before_action { respond_to :html }
...@@ -38,6 +40,12 @@ module WikiActions ...@@ -38,6 +40,12 @@ module WikiActions
feature: :track_unique_wiki_page_views, feature_default_enabled: true feature: :track_unique_wiki_page_views, feature_default_enabled: true
helper_method :view_file_button, :diff_file_html_data helper_method :view_file_button, :diff_file_html_data
rescue_from ::Gitlab::Git::CommandTimedOut do |exc|
raise exc unless RESCUE_GIT_TIMEOUTS_IN.include?(action_name)
render 'shared/wikis/git_error'
end
end end
def new def new
...@@ -46,11 +54,7 @@ module WikiActions ...@@ -46,11 +54,7 @@ module WikiActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def pages def pages
@wiki_pages = Kaminari.paginate_array( @wiki_entries = WikiDirectory.group_pages(wiki_pages)
wiki.list_pages(sort: params[:sort], direction: params[:direction])
).page(params[:page])
@wiki_entries = WikiDirectory.group_pages(@wiki_pages)
render 'shared/wikis/pages' render 'shared/wikis/pages'
end end
...@@ -225,9 +229,19 @@ module WikiActions ...@@ -225,9 +229,19 @@ module WikiActions
unless @sidebar_page # Fallback to default sidebar unless @sidebar_page # Fallback to default sidebar
@sidebar_wiki_entries, @sidebar_limited = wiki.sidebar_entries @sidebar_wiki_entries, @sidebar_limited = wiki.sidebar_entries
end end
rescue ::Gitlab::Git::CommandTimedOut => e
@sidebar_error = e
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
def wiki_pages
strong_memoize(:wiki_pages) do
Kaminari.paginate_array(
wiki.list_pages(sort: params[:sort], direction: params[:direction])
).page(params[:page])
end
end
def wiki_params def wiki_params
params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
end end
......
.gl-alert.gl-alert-info
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon')
.gl-alert-body
= body
...@@ -10,11 +10,14 @@ ...@@ -10,11 +10,14 @@
= sprite_icon('download', css_class: 'gl-mr-2') = sprite_icon('download', css_class: 'gl-mr-2')
%span= _("Clone repository") %span= _("Clone repository")
- if @sidebar_error.present?
= render 'shared/alert_info', body: s_('Wiki|The sidebar failed to load. You can reload the page to try again.')
.blocks-container .blocks-container
.block.block-first.w-100 .block.block-first.w-100
- if @sidebar_page - if @sidebar_page
= render_wiki_content(@sidebar_page) = render_wiki_content(@sidebar_page)
- else - elsif @sidebar_wiki_entries
%ul.wiki-pages %ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar' = render @sidebar_wiki_entries, context: 'sidebar'
.block.w-100 .block.w-100
......
- if @page
- wiki_page_title @page
- add_page_specific_style 'page_bundles/wiki'
- git_access_url = wiki_path(@wiki, action: :git_access)
.wiki-page-header.top-area.gl-flex-direction-column.gl-lg-flex-direction-row
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { qa_selector: 'wiki_page_title', testid: 'wiki_page_title' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content' } }
= _('The page could not be displayed because it timed out.')
= html_escape(_('You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}')) % { linkStart: "<a href=\"#{git_access_url}\">".html_safe, linkEnd: '</a>'.html_safe, cloneIcon: sprite_icon('download', css_class: 'gl-mr-2').html_safe }
---
title: Catch wiki timeouts when rendering pages
merge_request: 46627
author:
type: fixed
...@@ -17,6 +17,7 @@ module Gitlab ...@@ -17,6 +17,7 @@ module Gitlab
CommitError = Class.new(BaseError) CommitError = Class.new(BaseError)
OSError = Class.new(BaseError) OSError = Class.new(BaseError)
UnknownRef = Class.new(BaseError) UnknownRef = Class.new(BaseError)
CommandTimedOut = Class.new(CommandError)
class << self class << self
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
......
...@@ -9,6 +9,8 @@ module Gitlab ...@@ -9,6 +9,8 @@ module Gitlab
raise Gitlab::Git::Repository::NoRepository.new(e) raise Gitlab::Git::Repository::NoRepository.new(e)
rescue GRPC::InvalidArgument => e rescue GRPC::InvalidArgument => e
raise ArgumentError.new(e) raise ArgumentError.new(e)
rescue GRPC::DeadlineExceeded => e
raise Gitlab::Git::CommandTimedOut.new(e)
rescue GRPC::BadStatus => e rescue GRPC::BadStatus => e
raise Gitlab::Git::CommandError.new(e) raise Gitlab::Git::CommandError.new(e)
end end
......
...@@ -11438,6 +11438,9 @@ msgstr "" ...@@ -11438,6 +11438,9 @@ msgstr ""
msgid "Failed to reset key. Please try again." msgid "Failed to reset key. Please try again."
msgstr "" msgstr ""
msgid "Failed to retrieve page"
msgstr ""
msgid "Failed to save merge conflicts resolutions. Please try again!" msgid "Failed to save merge conflicts resolutions. Please try again!"
msgstr "" msgstr ""
...@@ -27031,6 +27034,9 @@ msgstr "" ...@@ -27031,6 +27034,9 @@ msgstr ""
msgid "The number of times an upload record could not find its file" msgid "The number of times an upload record could not find its file"
msgstr "" msgstr ""
msgid "The page could not be displayed because it timed out."
msgstr ""
msgid "The parent epic is confidential and can only contain confidential epics and issues" msgid "The parent epic is confidential and can only contain confidential epics and issues"
msgstr "" msgstr ""
...@@ -30710,6 +30716,9 @@ msgstr "" ...@@ -30710,6 +30716,9 @@ msgstr ""
msgid "Wiki|Pages" msgid "Wiki|Pages"
msgstr "" msgstr ""
msgid "Wiki|The sidebar failed to load. You can reload the page to try again."
msgstr ""
msgid "Wiki|Title" msgid "Wiki|Title"
msgstr "" msgstr ""
...@@ -31010,6 +31019,9 @@ msgstr "" ...@@ -31010,6 +31019,9 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}." msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "" msgstr ""
msgid "You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}"
msgstr ""
msgid "You cannot access the raw file. Please wait a minute." msgid "You cannot access the raw file. Please wait a minute."
msgstr "" msgstr ""
......
...@@ -14,6 +14,22 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -14,6 +14,22 @@ RSpec.shared_examples 'wiki controller actions' do
sign_in(user) sign_in(user)
end end
shared_examples 'recovers from git timeout' do
let(:method_name) { :page }
context 'when we encounter git command errors' do
it 'renders the appropriate template', :aggregate_failures do
expect(controller).to receive(method_name) do
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
end
request
expect(response).to render_template('shared/wikis/git_error')
end
end
end
describe 'GET #new' do describe 'GET #new' do
subject(:request) { get :new, params: routing_params } subject(:request) { get :new, params: routing_params }
...@@ -48,6 +64,12 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -48,6 +64,12 @@ RSpec.shared_examples 'wiki controller actions' do
get :pages, params: routing_params.merge(id: wiki_title) get :pages, params: routing_params.merge(id: wiki_title)
end end
it_behaves_like 'recovers from git timeout' do
subject(:request) { get :pages, params: routing_params.merge(id: wiki_title) }
let(:method_name) { :wiki_pages }
end
it 'assigns the page collections' do it 'assigns the page collections' do
expect(assigns(:wiki_pages)).to contain_exactly(an_instance_of(WikiPage)) expect(assigns(:wiki_pages)).to contain_exactly(an_instance_of(WikiPage))
expect(assigns(:wiki_entries)).to contain_exactly(an_instance_of(WikiPage)) expect(assigns(:wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
...@@ -99,6 +121,12 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -99,6 +121,12 @@ RSpec.shared_examples 'wiki controller actions' do
end end
end end
it_behaves_like 'recovers from git timeout' do
subject(:request) { get :history, params: routing_params.merge(id: wiki_title) }
let(:allow_read_wiki) { true }
end
it_behaves_like 'fetching history', :ok do it_behaves_like 'fetching history', :ok do
let(:allow_read_wiki) { true } let(:allow_read_wiki) { true }
...@@ -139,6 +167,10 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -139,6 +167,10 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
it_behaves_like 'recovers from git timeout' do
subject(:request) { get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id) }
end
end end
describe 'GET #show' do describe 'GET #show' do
...@@ -151,6 +183,8 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -151,6 +183,8 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when page exists' do context 'when page exists' do
let(:id) { wiki_title } let(:id) { wiki_title }
it_behaves_like 'recovers from git timeout'
it 'renders the page' do it 'renders the page' do
request request
...@@ -161,6 +195,28 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -161,6 +195,28 @@ RSpec.shared_examples 'wiki controller actions' do
expect(assigns(:sidebar_limited)).to be(false) expect(assigns(:sidebar_limited)).to be(false)
end end
context 'the sidebar fails to load' do
before do
allow(Wiki).to receive(:for_container).and_return(wiki)
wiki.wiki
expect(wiki).to receive(:find_sidebar) do
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
end
end
it 'renders the page, and marks the sidebar as failed' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('shared/wikis/_sidebar')
expect(assigns(:page).title).to eq(wiki_title)
expect(assigns(:sidebar_page)).to be_nil
expect(assigns(:sidebar_wiki_entries)).to be_nil
expect(assigns(:sidebar_limited)).to be_nil
expect(assigns(:sidebar_error)).to be_a_kind_of(::Gitlab::Git::CommandError)
end
end
context 'page view tracking' do context 'page view tracking' do
it_behaves_like 'tracking unique hll events', :track_unique_wiki_page_views do it_behaves_like 'tracking unique hll events', :track_unique_wiki_page_views do
let(:target_id) { 'wiki_action' } let(:target_id) { 'wiki_action' }
...@@ -308,6 +364,7 @@ RSpec.shared_examples 'wiki controller actions' do ...@@ -308,6 +364,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject(:request) { get(:edit, params: routing_params.merge(id: id_param)) } subject(:request) { get(:edit, params: routing_params.merge(id: id_param)) }
it_behaves_like 'edit action' it_behaves_like 'edit action'
it_behaves_like 'recovers from git timeout'
context 'when page content encoding is valid' do context 'when page content encoding is valid' do
render_views render_views
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'shared/wikis/_sidebar.html.haml' do
let_it_be(:project) { create(:project) }
let_it_be(:wiki) { Wiki.for_container(project, project.default_owner) }
before do
assign(:wiki, wiki)
assign(:project, project)
end
it 'includes a link to clone the repository' do
render
expect(rendered).to have_link('Clone repository')
end
context 'the wiki is not a project wiki' do
it 'does not include the clone repository link' do
allow(wiki).to receive(:container).and_return(create(:group))
render
expect(rendered).not_to have_link('Clone repository')
end
end
context 'the sidebar failed to load' do
before do
assign(:sidebar_error, Object.new)
end
it 'reports this to the user' do
render
expect(rendered).to include('The sidebar failed to load')
expect(rendered).to have_css('.gl-alert.gl-alert-info')
end
end
context 'The sidebar comes from a custom page' do
before do
assign(:sidebar_page, double('WikiPage', path: 'sidebar.md', slug: 'sidebar', content: 'Some sidebar content'))
end
it 'does not show an alert' do
render
expect(rendered).not_to include('The sidebar failed to load')
expect(rendered).not_to have_css('.gl-alert.gl-alert-info')
end
it 'renders the wiki content' do
render
expect(rendered).to include('Some sidebar content')
end
end
context 'The sidebar comes a list of wiki pages' do
before do
assign(:sidebar_wiki_entries, create_list(:wiki_page, 3, wiki: wiki))
assign(:sidebar_limited, true)
stub_template "../shared/wikis/_wiki_pages.html.erb" => "Entries: <%= @sidebar_wiki_entries.size %>"
stub_template "../shared/wikis/_wiki_page.html.erb" => 'A WIKI PAGE'
end
it 'does not show an alert' do
render
expect(rendered).not_to include('The sidebar failed to load')
expect(rendered).not_to have_css('.gl-alert.gl-alert-info')
end
it 'renders the wiki content' do
render
expect(rendered).to include('A WIKI PAGE' * 3)
expect(rendered).to have_link('View All Pages')
end
context 'there is no more to see' do
it 'does not invite the user to view more' do
assign(:sidebar_limited, false)
render
expect(rendered).not_to have_link('View All Pages')
end
end
end
end
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