Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-14 09:09:13 +00:00
parent e761659df2
commit efcfe56681
61 changed files with 527 additions and 938 deletions

View File

@ -119,6 +119,8 @@ gem 'net-ldap', '~> 0.16.3'
gem 'grape', '~> 1.5.2' gem 'grape', '~> 1.5.2'
gem 'grape-entity', '~> 0.10.0' gem 'grape-entity', '~> 0.10.0'
gem 'rack-cors', '~> 1.1.1', require: 'rack/cors' gem 'rack-cors', '~> 1.1.1', require: 'rack/cors'
gem 'grape-swagger', '~>1.5.0', group: [:development, :test]
gem 'grape-swagger-entity', '~> 0.5.1', group: [:development, :test]
# GraphQL API # GraphQL API
gem 'graphql', '~> 1.13.12' gem 'graphql', '~> 1.13.12'

View File

@ -231,6 +231,8 @@
{"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"}, {"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"},
{"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"}, {"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"},
{"name":"grape-path-helpers","version":"1.7.1","platform":"ruby","checksum":"2e27271a20d4073e3a3b2b955425c7f803e198be3ba8f6e59e3d59643c5381e2"}, {"name":"grape-path-helpers","version":"1.7.1","platform":"ruby","checksum":"2e27271a20d4073e3a3b2b955425c7f803e198be3ba8f6e59e3d59643c5381e2"},
{"name":"grape-swagger","version":"1.5.0","platform":"ruby","checksum":"9c885b326ab0644abecf7df4ce866abc2411f359cfd59cbcca545b9b3b25c8ff"},
{"name":"grape-swagger-entity","version":"0.5.1","platform":"ruby","checksum":"f51e372d00ac96cf90d948f87b3f4eb287ab053976ca57ad503d442ad8605523"},
{"name":"grape_logging","version":"1.8.4","platform":"ruby","checksum":"efcc3e322dbd5d620a68f078733b7db043cf12680144cd03c982f14115c792d1"}, {"name":"grape_logging","version":"1.8.4","platform":"ruby","checksum":"efcc3e322dbd5d620a68f078733b7db043cf12680144cd03c982f14115c792d1"},
{"name":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"}, {"name":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"},
{"name":"graphlient","version":"0.5.0","platform":"ruby","checksum":"0f2c9416142e50b6bd4edcd86fe6810f792951732c487f9061aee6d420e0f292"}, {"name":"graphlient","version":"0.5.0","platform":"ruby","checksum":"0f2c9416142e50b6bd4edcd86fe6810f792951732c487f9061aee6d420e0f292"},

View File

@ -642,6 +642,11 @@ GEM
grape (~> 1.3) grape (~> 1.3)
rake (> 12) rake (> 12)
ruby2_keywords (~> 0.0.2) ruby2_keywords (~> 0.0.2)
grape-swagger (1.5.0)
grape (~> 1.3)
grape-swagger-entity (0.5.1)
grape-entity (>= 0.6.0)
grape-swagger (>= 1.2.0)
grape_logging (1.8.4) grape_logging (1.8.4)
grape grape
rack rack
@ -1638,6 +1643,8 @@ DEPENDENCIES
grape (~> 1.5.2) grape (~> 1.5.2)
grape-entity (~> 0.10.0) grape-entity (~> 0.10.0)
grape-path-helpers (~> 1.7.1) grape-path-helpers (~> 1.7.1)
grape-swagger (~> 1.5.0)
grape-swagger-entity (~> 0.5.1)
grape_logging (~> 1.8) grape_logging (~> 1.8)
graphiql-rails (~> 1.8) graphiql-rails (~> 1.8)
graphlient (~> 0.5.0) graphlient (~> 0.5.0)

View File

@ -190,7 +190,10 @@ export default {
<div class="media" :class="{ 'gl-cursor-pointer': isCollapsible }" @click="toggleCollapsed"> <div class="media" :class="{ 'gl-cursor-pointer': isCollapsible }" @click="toggleCollapsed">
<status-icon :status="statusIconName" :size="24" class="align-self-center" /> <status-icon :status="statusIconName" :size="24" class="align-self-center" />
<div class="media-body gl-display-flex gl-align-items-flex-start gl-flex-direction-row!"> <div class="media-body gl-display-flex gl-align-items-flex-start gl-flex-direction-row!">
<div data-testid="report-section-code-text" class="js-code-text code-text"> <div
data-testid="report-section-code-text"
class="js-code-text code-text gl-align-self-center gl-flex-grow-1"
>
<div class="gl-display-flex gl-align-items-center"> <div class="gl-display-flex gl-align-items-center">
<p class="gl-line-height-normal gl-m-0">{{ headerText }}</p> <p class="gl-line-height-normal gl-m-0">{{ headerText }}</p>
<slot :name="slotName"></slot> <slot :name="slotName"></slot>

View File

@ -209,9 +209,7 @@ module WikiActions
def wiki def wiki
strong_memoize(:wiki) do strong_memoize(:wiki) do
wiki = Wiki.for_container(container, current_user) wiki = Wiki.for_container(container, current_user)
wiki.create_wiki_repository
# Call #wiki to make sure the Wiki Repo is initialized
wiki.wiki
wiki wiki
end end
@ -242,7 +240,7 @@ module WikiActions
def wiki_pages def wiki_pages
strong_memoize(:wiki_pages) do strong_memoize(:wiki_pages) do
Kaminari.paginate_array( Kaminari.paginate_array(
wiki.list_pages(sort: params[:sort], direction: params[:direction]) wiki.list_pages(direction: params[:direction])
).page(params[:page]) ).page(params[:page])
end end
end end

View File

@ -59,15 +59,13 @@ module WikiHelper
end end
end end
def wiki_sort_controls(wiki, sort, direction) def wiki_sort_controls(wiki, direction)
sort ||= Wiki::TITLE_ORDER
link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
reversed_direction = direction == 'desc' ? 'asc' : 'desc' reversed_direction = direction == 'desc' ? 'asc' : 'desc'
icon_class = direction == 'desc' ? 'highest' : 'lowest' icon_class = direction == 'desc' ? 'highest' : 'lowest'
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending') title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
link_options = { action: :pages, direction: reversed_direction } link_options = { action: :pages, direction: reversed_direction }
link_options[:sort] = sort unless wiki.disable_sorting?
link_to(wiki_path(wiki, **link_options), link_to(wiki_path(wiki, **link_options),
type: 'button', class: link_class, title: title) do type: 'button', class: link_class, title: title) do

View File

@ -8,7 +8,7 @@ module HasWiki
end end
def create_wiki def create_wiki
wiki.wiki wiki.create_wiki_repository
true true
rescue Wiki::CouldNotCreateWikiError rescue Wiki::CouldNotCreateWikiError
errors.add(:base, _('Failed to create wiki')) errors.add(:base, _('Failed to create wiki'))

View File

@ -9,6 +9,8 @@ class Wiki
extend ActiveModel::Naming extend ActiveModel::Naming
DuplicatePageError = Class.new(StandardError)
MARKUPS = { # rubocop:disable Style/MultilineIfModifier MARKUPS = { # rubocop:disable Style/MultilineIfModifier
markdown: { markdown: {
name: 'Markdown', name: 'Markdown',
@ -114,6 +116,29 @@ class Wiki
title = Pathname.new(title).relative_path_from('/').to_s title = Pathname.new(title).relative_path_from('/').to_s
title.tr(' ', '-') title.tr(' ', '-')
end end
def canonicalize_filename(filename)
::File.basename(filename, ::File.extname(filename)).tr('-', ' ')
end
def cname(name, char_white_sub = '-', char_other_sub = '-')
name.to_s.gsub(/\s/, char_white_sub).gsub(/[<>+]/, char_other_sub)
end
def preview_slug(title, format)
ext = format == :markdown ? "md" : format.to_s
name = cname(title) + '.' + ext
canonical_name = canonicalize_filename(name)
path =
if name.include?('/')
name.sub(%r{/[^/]+$}, '/')
else
''
end
path + cname(canonical_name, '-', '-')
end
end end
def initialize(container, user = nil) def initialize(container, user = nil)
@ -145,14 +170,6 @@ class Wiki
container.path + '.wiki' container.path + '.wiki'
end end
# Returns the Gitlab::Git::Wiki object.
def wiki
strong_memoize(:wiki) do
create_wiki_repository
Gitlab::Git::Wiki.new(repository.raw)
end
end
def create_wiki_repository def create_wiki_repository
repository.create_if_not_exists(default_branch) repository.create_if_not_exists(default_branch)
@ -190,13 +207,9 @@ class Wiki
# #
# Returns an Array of GitLab WikiPage instances or an # Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages. # empty Array if this Wiki has no pages.
def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false) def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false)
if list_pages_with_repository_rpcs? create_wiki_repository unless repository_exists?
create_wiki_repository unless repository_exists? list_pages_with_repository_rpcs(limit: limit, direction: direction, load_content: load_content)
list_pages_with_repository_rpcs(limit: limit, sort: sort, direction: direction, load_content: load_content)
else
list_pages_with_legacy_wiki_service(limit: limit, sort: sort, direction: direction, load_content: load_content)
end
end end
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options) def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
@ -215,12 +228,8 @@ class Wiki
# #
# Returns an initialized WikiPage instance or nil # Returns an initialized WikiPage instance or nil
def find_page(title, version = nil, load_content: true) def find_page(title, version = nil, load_content: true)
if find_page_with_repository_rpcs? create_wiki_repository unless repository_exists?
create_wiki_repository unless repository_exists? find_page_with_repository_rpcs(title, version, load_content: load_content)
find_page_with_repository_rpcs(title, version, load_content: load_content)
else
find_page_with_legacy_wiki_service(title, version, load_content: load_content)
end
end end
def find_sidebar(version = nil) def find_sidebar(version = nil)
@ -254,7 +263,7 @@ class Wiki
raise_duplicate_page_error! raise_duplicate_page_error!
end end
end end
rescue Gitlab::Git::Wiki::DuplicatePageError => e rescue DuplicatePageError => e
@error_message = _("Duplicate page: %{error_message}" % { error_message: e.message }) @error_message = _("Duplicate page: %{error_message}" % { error_message: e.message })
false false
@ -270,6 +279,7 @@ class Wiki
extension = page.format != format.to_sym ? default_extension : File.extname(page.path).downcase[1..] extension = page.format != format.to_sym ? default_extension : File.extname(page.path).downcase[1..]
capture_git_error(:updated) do capture_git_error(:updated) do
create_wiki_repository unless repository_exists?
repository.update_file( repository.update_file(
user, user,
sluggified_full_path(title, extension), sluggified_full_path(title, extension),
@ -288,6 +298,7 @@ class Wiki
return unless page return unless page
capture_git_error(:deleted) do capture_git_error(:deleted) do
create_wiki_repository unless repository_exists?
repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title)) repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title))
after_wiki_activity after_wiki_activity
@ -304,8 +315,10 @@ class Wiki
[title, title_array.join("/")] [title, title_array.join("/")]
end end
# TODO: This method is redundant. Should be replaced by create_wiki_repository
def ensure_repository def ensure_repository
raise CouldNotCreateWikiError unless wiki.repository_exists? create_wiki_repository
raise CouldNotCreateWikiError unless repository_exists?
end end
def hook_attrs def hook_attrs
@ -341,7 +354,7 @@ class Wiki
override :default_branch override :default_branch
def default_branch def default_branch
super || Gitlab::Git::Wiki.default_ref(container) super || Gitlab::DefaultBranch.value(object: container)
end end
def wiki_base_path def wiki_base_path
@ -381,10 +394,6 @@ class Wiki
false false
end end
def disable_sorting?
list_pages_with_repository_rpcs?
end
private private
def multi_commit_options(action, message = nil, title = nil) def multi_commit_options(action, message = nil, title = nil)
@ -429,7 +438,7 @@ class Wiki
end end
def raise_duplicate_page_error! def raise_duplicate_page_error!
raise Gitlab::Git::Wiki::DuplicatePageError, _('A page with that title already exists') raise ::Wiki::DuplicatePageError, _('A page with that title already exists')
end end
def sluggified_full_path(title, extension) def sluggified_full_path(title, extension)
@ -441,15 +450,7 @@ class Wiki
end end
def canonicalize_filename(filename) def canonicalize_filename(filename)
Gitlab::Git::Wiki::GollumSlug.canonicalize_filename(filename) self.class.canonicalize_filename(filename)
end
def find_page_with_legacy_wiki_service(title, version, load_content: false)
page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(title: page_title, version: version, dir: page_dir, load_content: load_content)
WikiPage.new(self, page)
end
end end
def find_matched_file(title, version) def find_matched_file(title, version)
@ -494,17 +495,6 @@ class Wiki
WikiPage.new(self, page) WikiPage.new(self, page)
end end
def find_page_with_repository_rpcs?
group =
if container.is_a?(::Group)
container
else
container.group
end
Feature.enabled?(:wiki_find_page_with_normal_repository_rpcs, group, type: :development)
end
def file_extension_regexp def file_extension_regexp
# We could not use ALLOWED_EXTENSIONS_REGEX constant or similar regexp with # We could not use ALLOWED_EXTENSIONS_REGEX constant or similar regexp with
# Regexp.union. The result combination complicated modifiers: # Regexp.union. The result combination complicated modifiers:
@ -519,17 +509,6 @@ class Wiki
path.sub(/\.[^.]+\z/, "") path.sub(/\.[^.]+\z/, "")
end end
def list_pages_with_repository_rpcs?
group =
if container.is_a?(::Group)
container
else
container.group
end
Feature.enabled?(:wiki_list_pages_with_normal_repository_rpcs, group, type: :development)
end
def list_page_paths def list_page_paths
return [] if repository.empty? return [] if repository.empty?
@ -537,7 +516,7 @@ class Wiki
repository.search_files_by_regexp(path_regexp, default_branch) repository.search_files_by_regexp(path_regexp, default_branch)
end end
def list_pages_with_repository_rpcs(limit:, sort:, direction:, load_content:) def list_pages_with_repository_rpcs(limit:, direction:, load_content:)
paths = list_page_paths paths = list_page_paths
return [] if paths.empty? return [] if paths.empty?
@ -553,29 +532,18 @@ class Wiki
) )
WikiPage.new(self, page) WikiPage.new(self, page)
end end
sort_pages!(pages, sort, direction) sort_pages!(pages, direction)
pages = pages.take(limit) if limit > 0 pages = pages.take(limit) if limit > 0
fetch_pages_content!(pages) if load_content fetch_pages_content!(pages) if load_content
pages pages
end end
def list_pages_with_legacy_wiki_service(limit:, sort:, direction:, load_content:)
wiki.list_pages(
limit: limit,
sort: sort,
direction_desc: direction == DIRECTION_DESC,
load_content: load_content
).map do |page|
WikiPage.new(self, page)
end
end
# After migrating to normal repository RPCs, it's very expensive to sort the # After migrating to normal repository RPCs, it's very expensive to sort the
# pages by created_at. We have to either ListLastCommitsForTree RPC call or # pages by created_at. We have to either ListLastCommitsForTree RPC call or
# N+1 LastCommitForPath. Either are efficient for a large repository. # N+1 LastCommitForPath. Either are efficient for a large repository.
# Therefore, we decide to sort the title only. # Therefore, we decide to sort the title only.
def sort_pages!(pages, _sort, direction) def sort_pages!(pages, direction)
# Sort by path to ensure the files inside a sub-folder are grouped and sorted together # Sort by path to ensure the files inside a sub-folder are grouped and sorted together
pages.sort_by!(&:path) pages.sort_by!(&:path)
pages.reverse! if direction == DIRECTION_DESC pages.reverse! if direction == DIRECTION_DESC

View File

@ -73,7 +73,7 @@ class WikiPage
# The escaped URL path of this page. # The escaped URL path of this page.
def slug def slug
attributes[:slug].presence || wiki.wiki.preview_slug(title, format) attributes[:slug].presence || ::Wiki.preview_slug(title, format)
end end
alias_method :id, :slug # required to use build_stubbed alias_method :id, :slug # required to use build_stubbed

View File

@ -1,5 +1,7 @@
- page_title s_('WorkItem|Work Items') - page_title s_('WorkItem|Work Items')
- add_page_specific_style 'page_bundles/work_items' - add_page_specific_style 'page_bundles/work_items'
- @gfm_form = true
- @noteable_type = 'WorkItem'
#js-work-items{ data: work_items_index_data(@project) } #js-work-items{ data: work_items_index_data(@project) }
= render 'projects/invite_members_modal', project: @project = render 'projects/invite_members_modal', project: @project

View File

@ -4,6 +4,6 @@
%div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' } %div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' }
%span.gl-display-flex.gl-align-items-center %span.gl-display-flex.gl-align-items-center
= link_to wiki_blob_link, data: { track_action: 'click_text', track_label: "wiki_title", track_property: 'search_result' }, class: 'gl-w-full' do = link_to wiki_blob_link, data: { track_action: 'click_text', track_label: "wiki_title", track_property: 'search_result' }, class: 'gl-w-full' do
%span.term.str-truncated.gl-font-weight-bold= ::Gitlab::Git::Wiki::GollumSlug.canonicalize_filename(wiki_blob.path) %span.term.str-truncated.gl-font-weight-bold= ::Wiki.canonicalize_filename(wiki_blob.path)
.description.term.col-sm-10.gl-px-0 .description.term.col-sm-10.gl-px-0
= simple_search_highlight_and_truncate(wiki_blob.data, @search_term) = simple_search_highlight_and_truncate(wiki_blob.data, @search_term)

View File

@ -2,7 +2,6 @@
- breadcrumb_title s_("Wiki|Pages") - breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki") - page_title s_("Wiki|Pages"), _("Wiki")
- add_page_specific_style 'page_bundles/wiki' - add_page_specific_style 'page_bundles/wiki'
- wiki_sort_options = [{ text: s_("Wiki|Title"), value: 'title', href: wiki_path(@wiki, action: :pages, sort: Wiki::TITLE_ORDER)}, { text: s_("Wiki|Created date"), value: 'created_at', href: wiki_path(@wiki, action: :pages, sort: Wiki::CREATED_AT_ORDER) }]
.wiki-page-header.top-area.flex-column.flex-lg-row .wiki-page-header.top-area.flex-column.flex-lg-row
%h1.page-title.gl-font-size-h-display.gl-flex-grow-1 %h1.page-title.gl-font-size-h-display.gl-flex-grow-1
@ -15,9 +14,7 @@
.dropdown.inline.wiki-sort-dropdown .dropdown.inline.wiki-sort-dropdown
.btn-group{ role: 'group' } .btn-group{ role: 'group' }
- unless @wiki.disable_sorting? = wiki_sort_controls(@wiki, params[:direction])
= gl_redirect_listbox_tag wiki_sort_options, params[:sort], data: { right: true }
= wiki_sort_controls(@wiki, params[:sort], params[:direction])
%ul.wiki-pages-list.content-list %ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages' = render @wiki_entries, context: 'pages'

View File

@ -1,8 +0,0 @@
---
name: wiki_find_page_with_normal_repository_rpcs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95897
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371487
milestone: '15.4'
type: development
group: group::gitaly
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: wiki_list_pages_with_normal_repository_rpcs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96461
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372259
milestone: '15.4'
type: development
group: group::gitaly
default_enabled: true

View File

@ -10,8 +10,11 @@ value_type: number
status: active status: active
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- users_viewing_analytics_group_devops_adoption - users_viewing_analytics_group_devops_adoption
- i_analytics_dev_ops_adoption - i_analytics_dev_ops_adoption

View File

@ -10,8 +10,11 @@ value_type: number
status: active status: active
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- g_project_management_issue_title_changed - g_project_management_issue_title_changed
- g_project_management_issue_description_changed - g_project_management_issue_description_changed

View File

@ -12,8 +12,11 @@ milestone: "13.12"
introduced_by_url: introduced_by_url:
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- o_pipeline_authoring_unique_users_committing_ciconfigfile - o_pipeline_authoring_unique_users_committing_ciconfigfile
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile - o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile

View File

@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll
data_category: optional data_category: optional
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- error_tracking_view_list - error_tracking_view_list
- error_tracking_view_details - error_tracking_view_details

View File

@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll
data_category: optional data_category: optional
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- incident_management_timeline_event_created - incident_management_timeline_event_created
- incident_management_timeline_event_edited - incident_management_timeline_event_edited

View File

@ -10,8 +10,11 @@ value_type: number
status: active status: active
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- users_viewing_analytics_group_devops_adoption - users_viewing_analytics_group_devops_adoption
- i_analytics_dev_ops_adoption - i_analytics_dev_ops_adoption

View File

@ -10,8 +10,11 @@ value_type: number
status: active status: active
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- g_project_management_issue_title_changed - g_project_management_issue_title_changed
- g_project_management_issue_description_changed - g_project_management_issue_description_changed

View File

@ -12,8 +12,11 @@ milestone: "13.12"
introduced_by_url: introduced_by_url:
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- o_pipeline_authoring_unique_users_committing_ciconfigfile - o_pipeline_authoring_unique_users_committing_ciconfigfile
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile - o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile

View File

@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll
data_category: optional data_category: optional
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- error_tracking_view_list - error_tracking_view_list
- error_tracking_view_details - error_tracking_view_details

View File

@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll
data_category: optional data_category: optional
instrumentation_class: RedisHLLMetric instrumentation_class: AggregatedMetric
options: options:
aggregate:
operator: OR
attribute: user_id
events: events:
- incident_management_timeline_event_created - incident_management_timeline_event_created
- incident_management_timeline_event_edited - incident_management_timeline_event_edited

18
config/open_api.yml Normal file
View File

@ -0,0 +1,18 @@
metadata:
doc_version: v4
info:
title: GitLab API
termsOfService: https://about.gitlab.com/terms/
host: gitlab.com
security_definitions:
access_token_header:
type: apiKey
name: PRIVATE-TOKEN
in: header
access_token_query:
type: apiKey
name: private_token
in: query
tags:
- name: metadata
description: Operations related to metadata of the GitLab instance

View File

@ -0,0 +1,90 @@
---
info:
title: GitLab API
version: v4
swagger: '2.0'
produces:
- application/json
securityDefinitions:
access_token_header:
type: apiKey
name: PRIVATE-TOKEN
in: header
access_token_query:
type: apiKey
name: private_token
in: query
host: gitlab.com
tags:
- name: metadata
description: Operations related to metadata of the GitLab instance
paths:
"/api/v4/metadata":
get:
summary: Retrieve metadata information for this GitLab instance.
description: This feature was introduced in GitLab 15.2.
produces:
- application/json
responses:
'200':
description: successful operation
schema:
"$ref": "#/definitions/API_Entities_Metadata"
examples:
successful_response:
value:
version: 15.0-pre
revision: c401a659d0c
kas:
enabled: true
externalUrl: grpc://gitlab.example.com:8150
version: 15.0.0
'401':
description: unauthorized operation
tags:
- metadata
operationId: getApiV4Metadata
"/api/v4/version":
get:
summary: Get the version information of the GitLab instance.
description: This feature was introduced in GitLab 8.13 and deprecated in 15.5.
We recommend you instead use the Metadata API.
produces:
- application/json
responses:
'200':
description: successful operation
schema:
"$ref": "#/definitions/API_Entities_Metadata"
examples:
Example:
value:
version: 15.0-pre
revision: c401a659d0c
kas:
enabled: true
externalUrl: grpc://gitlab.example.com:8150
version: 15.0.0
'401':
description: unauthorized operation
tags:
- metadata
operationId: getApiV4Version
definitions:
API_Entities_Metadata:
type: object
properties:
version:
type: string
revision:
type: string
kas:
type: object
properties:
enabled:
type: boolean
externalUrl:
type: string
version:
type: string
description: API_Entities_Metadata model

View File

@ -50,7 +50,7 @@ For example, consider a migration that creates a table with two text columns,
`db/migrate/20200401000001_create_db_guides.rb`: `db/migrate/20200401000001_create_db_guides.rb`:
```ruby ```ruby
class CreateDbGuides < Gitlab::Database::Migration[1.0] class CreateDbGuides < Gitlab::Database::Migration[2.0]
def change def change
create_table :db_guides do |t| create_table :db_guides do |t|
t.bigint :stars, default: 0, null: false t.bigint :stars, default: 0, null: false
@ -74,7 +74,7 @@ For example, consider a migration that adds a new text column `extended_title` t
`db/migrate/20200501000001_add_extended_title_to_sprints.rb`: `db/migrate/20200501000001_add_extended_title_to_sprints.rb`:
```ruby ```ruby
class AddExtendedTitleToSprints < Gitlab::Database::Migration[1.0] class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.0]
# rubocop:disable Migration/AddLimitToTextColumns # rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20200501000002_add_text_limit_to_sprints_extended_title # limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
@ -89,7 +89,7 @@ A second migration should follow the first one with a limit added to `extended_t
`db/migrate/20200501000002_add_text_limit_to_sprints_extended_title.rb`: `db/migrate/20200501000002_add_text_limit_to_sprints_extended_title.rb`:
```ruby ```ruby
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[1.0] class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.0]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
@ -165,7 +165,7 @@ in a post-deployment migration,
`db/post_migrate/20200501000001_add_text_limit_migration.rb`: `db/post_migrate/20200501000001_add_text_limit_migration.rb`:
```ruby ```ruby
class AddTextLimitMigration < Gitlab::Database::Migration[1.0] class AddTextLimitMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
@ -236,7 +236,7 @@ helper in a final post-deployment migration,
`db/post_migrate/20200601000001_validate_text_limit_migration.rb`: `db/post_migrate/20200601000001_validate_text_limit_migration.rb`:
```ruby ```ruby
class ValidateTextLimitMigration < Gitlab::Database::Migration[1.0] class ValidateTextLimitMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
@ -255,7 +255,7 @@ Increasing text limits on existing database columns can be safely achieved by fi
and then dropping the previous limit: and then dropping the previous limit:
```ruby ```ruby
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[1.0] class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[2.0]
disable_ddl_transaction! disable_ddl_transaction!
def up def up

View File

@ -139,5 +139,5 @@ end
``` ```
The `ApplicationRecord` class uses a different database connection than the `Ci::Build` records. The `ApplicationRecord` class uses a different database connection than the `Ci::Build` records.
The two statements in the transaction block are not part of the transaction and are The two statements in the transaction block are not part of the transaction and are not
rolled back in case something goes wrong. They act as 3rd part calls. rolled back in case something goes wrong. They act as third-party calls.

View File

@ -9,6 +9,10 @@ comments: false
GitLab can be integrated with external services for enhanced functionality. GitLab can be integrated with external services for enhanced functionality.
## Services
Services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack are available as [integrations](../user/project/integrations/index.md).
## Issue trackers ## Issue trackers
You can use an [external issue tracker](external-issue-tracker.md) at the same time as the GitLab You can use an [external issue tracker](external-issue-tracker.md) at the same time as the GitLab
@ -61,10 +65,6 @@ or [Kroki](../administration/integration/kroki.md) to use diagrams in AsciiDoc a
- Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md). - Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md).
- Add [Elasticsearch](advanced_search/elasticsearch.md) for [Advanced Search](../user/search/advanced_search.md). - Add [Elasticsearch](advanced_search/elasticsearch.md) for [Advanced Search](../user/search/advanced_search.md).
## Integrations
Integration with services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack are available as [Integrations](../user/project/integrations/index.md).
## Troubleshooting ## Troubleshooting
### SSL certificate errors ### SSL certificate errors

View File

@ -152,6 +152,8 @@ You need at least the Developer role to edit a wiki page:
1. Edit the content. 1. Edit the content.
1. Select **Save changes**. 1. Select **Save changes**.
Unsaved changes to a wiki page are preserved in local browser storage to prevent accidental data loss.
### Create a table of contents ### Create a table of contents
To generate a table of contents from a wiki page's subheadings, use the `[[_TOC_]]` tag. To generate a table of contents from a wiki page's subheadings, use the `[[_TOC_]]` tag.

View File

@ -3,6 +3,7 @@
module API module API
class API < ::API::Base class API < ::API::Base
include APIGuard include APIGuard
include Helpers::OpenApi
LOG_FILENAME = Rails.root.join("log", "api_json.log") LOG_FILENAME = Rails.root.join("log", "api_json.log")
@ -165,6 +166,13 @@ module API
::Users::ActivityService.new(@current_user).execute ::Users::ActivityService.new(@current_user).execute
end end
# Mount endpoints to include in the OpenAPI V2 documentation here
namespace do
mount ::API::Metadata
add_open_api_documentation!
end
# Keep in alphabetical order # Keep in alphabetical order
mount ::API::AccessRequests mount ::API::AccessRequests
mount ::API::Admin::BatchedBackgroundMigrations mount ::API::Admin::BatchedBackgroundMigrations
@ -250,7 +258,6 @@ module API
mount ::API::MergeRequestApprovals mount ::API::MergeRequestApprovals
mount ::API::MergeRequestDiffs mount ::API::MergeRequestDiffs
mount ::API::MergeRequests mount ::API::MergeRequests
mount ::API::Metadata
mount ::API::Metrics::Dashboard::Annotations mount ::API::Metrics::Dashboard::Annotations
mount ::API::Metrics::UserStarredDashboards mount ::API::Metrics::UserStarredDashboards
mount ::API::Namespaces mount ::API::Namespaces

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module API
module Entities
class Metadata < Grape::Entity
expose :version
expose :revision
expose :kas do
expose :enabled, documentation: { type: 'boolean' }
expose :externalUrl
expose :version
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module API
module Helpers
module OpenApi
extend ActiveSupport::Concern
class_methods do
def add_open_api_documentation!
return if Rails.env.production?
open_api_config = YAML.load_file(Rails.root.join('config/open_api.yml'))['metadata'].deep_symbolize_keys
add_swagger_documentation(open_api_config)
end
end
end
end
end

View File

@ -35,8 +35,30 @@ module API
end end
end end
desc 'Get the metadata information of the GitLab instance.' do desc 'Retrieve metadata information for this GitLab instance.' do
detail 'This feature was introduced in GitLab 15.2.' detail 'This feature was introduced in GitLab 15.2.'
success [
{
code: 200,
model: Entities::Metadata,
message: 'successful operation',
examples: {
successful_response: {
'value' => {
version: "15.0-pre",
revision: "c401a659d0c",
kas: {
enabled: true,
externalUrl: "grpc://gitlab.example.com:8150",
version: "15.0.0"
}
}
}
}
}
]
failure [{ code: 401, message: 'unauthorized operation' }]
tags %w[metadata]
end end
get '/metadata' do get '/metadata' do
run_metadata_query run_metadata_query
@ -47,7 +69,30 @@ module API
desc 'Get the version information of the GitLab instance.' do desc 'Get the version information of the GitLab instance.' do
detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \ detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \
'We recommend you instead use the Metadata API.' 'We recommend you instead use the Metadata API.'
success [
{
code: 200,
model: Entities::Metadata,
message: 'successful operation',
examples: {
'Example' => {
'value' => {
version: "15.0-pre",
revision: "c401a659d0c",
kas: {
enabled: true,
externalUrl: "grpc://gitlab.example.com:8150",
version: "15.0.0"
}
}
}
}
}
]
failure [{ code: 401, message: 'unauthorized operation' }]
tags %w[metadata]
end end
get '/version' do get '/version' do
run_metadata_query run_metadata_query
end end

View File

@ -33,3 +33,8 @@ test:
stage: test stage: test
script: script:
- ./runmytests.sh - ./runmytests.sh
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -28,3 +28,8 @@ test:
# If you need to run any migrations or configure the database, this # If you need to run any migrations or configure the database, this
# would be the point to do it. # would be the point to do it.
- lein test - lein test
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -42,3 +42,8 @@ spec:
minitest: minitest:
script: script:
- crystal test/spec_test.cr # change to the file(s) you execute for tests - crystal test/spec_test.cr # change to the file(s) you execute for tests
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -74,3 +74,8 @@ django-tests:
- echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql - echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
# use python3 explicitly. see https://wiki.ubuntu.com/Python/3 # use python3 explicitly. see https://wiki.ubuntu.com/Python/3
- python3 manage.py test - python3 manage.py test
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -111,3 +111,8 @@ tests:
# (e.g. integration tests, unit tests etc). # (e.g. integration tests, unit tests etc).
script: script:
- 'dotnet test --no-restore' - 'dotnet test --no-restore'
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -1,127 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Git
class Wiki
include Gitlab::Git::WrapsGitalyErrors
DuplicatePageError = Class.new(StandardError)
DEFAULT_PAGINATION = Kaminari.config.default_per_page
CommitDetails = Struct.new(:user_id, :username, :name, :email, :message) do
def to_h
{ user_id: user_id, username: username, name: name, email: email, message: message }
end
end
# GollumSlug inlines just enough knowledge from Gollum::Page to generate a
# slug, which is used when previewing pages that haven't been persisted
class GollumSlug
class << self
def cname(name, char_white_sub = '-', char_other_sub = '-')
if name.respond_to?(:gsub)
name.gsub(/\s/, char_white_sub).gsub(/[<>+]/, char_other_sub)
else
''
end
end
def format_to_ext(format)
format == :markdown ? "md" : format.to_s
end
def canonicalize_filename(filename)
::File.basename(filename, ::File.extname(filename)).tr('-', ' ')
end
def generate(title, format)
ext = format_to_ext(format.to_sym)
name = cname(title) + '.' + ext
canonical_name = canonicalize_filename(name)
path =
if name.include?('/')
name.sub(%r{/[^/]+$}, '/')
else
''
end
path + cname(canonical_name, '-', '-')
end
end
end
attr_reader :repository
# TODO remove argument when issue
# https://gitlab.com/gitlab-org/gitlab/-/issues/329190
# is closed.
def self.default_ref(container = nil)
Gitlab::DefaultBranch.value(object: container)
end
# Initialize with a Gitlab::Git::Repository instance
def initialize(repository)
@repository = repository
end
def repository_exists?
@repository.exists?
end
def list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
wrapped_gitaly_errors do
gitaly_list_pages(
limit: limit,
sort: sort,
direction_desc: direction_desc,
load_content: load_content
)
end
end
def page(title:, version: nil, dir: nil, load_content: true)
wrapped_gitaly_errors do
gitaly_find_page(title: title, version: version, dir: dir, load_content: load_content)
end
end
def preview_slug(title, format)
GollumSlug.generate(title, format)
end
private
def gitaly_wiki_client
@gitaly_wiki_client ||= Gitlab::GitalyClient::WikiService.new(@repository)
end
def gitaly_find_page(title:, version: nil, dir: nil, load_content: true)
return unless title.present?
wiki_page, version = gitaly_wiki_client.find_page(title: title, version: version, dir: dir, load_content: load_content)
return unless wiki_page
Gitlab::Git::WikiPage.from_gitaly_wiki_page(wiki_page, version)
rescue GRPC::InvalidArgument
nil
end
def gitaly_list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
params = { limit: limit, sort: sort, direction_desc: direction_desc }
gitaly_pages =
if load_content
gitaly_wiki_client.load_all_pages(**params)
else
gitaly_wiki_client.list_all_pages(**params)
end
gitaly_pages.map do |wiki_page, version|
Gitlab::Git::WikiPage.from_gitaly_wiki_page(wiki_page, version)
end
end
end
end
end

View File

@ -5,22 +5,6 @@ module Gitlab
class WikiPage class WikiPage
attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :historical, :formatted_data attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :historical, :formatted_data
class << self
# Abstracts away Gitlab::GitalyClient::WikiPage
def from_gitaly_wiki_page(gitaly_page, version)
new(
url_path: gitaly_page.url_path,
title: gitaly_page.title,
format: gitaly_page.format,
path: gitaly_page.path,
raw_data: gitaly_page.raw_data,
name: gitaly_page.name,
historical: gitaly_page.historical?,
version: version
)
end
end
def initialize(hash) def initialize(hash)
@url_path = hash[:url_path] @url_path = hash[:url_path]
@title = hash[:title] @title = hash[:title]

View File

@ -1,169 +0,0 @@
# frozen_string_literal: true
require 'stringio'
module Gitlab
module GitalyClient
class WikiService
include Gitlab::EncodingHelper
MAX_MSG_SIZE = 128.kilobytes.freeze
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@repository = repository
end
def write_page(name, format, content, commit_details)
request = Gitaly::WikiWritePageRequest.new(
repository: @gitaly_repo,
name: encode_binary(name),
format: format.to_s,
commit_details: gitaly_commit_details(commit_details)
)
strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
request.content = strio.read(MAX_MSG_SIZE)
y.yield request
request = Gitaly::WikiWritePageRequest.new
end
end
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_write_page, enum, timeout: GitalyClient.medium_timeout)
if error = response.duplicate_error.presence
raise Gitlab::Git::Wiki::DuplicatePageError, error
end
end
def update_page(page_path, title, format, content, commit_details)
request = Gitaly::WikiUpdatePageRequest.new(
repository: @gitaly_repo,
page_path: encode_binary(page_path),
title: encode_binary(title),
format: format.to_s,
commit_details: gitaly_commit_details(commit_details)
)
strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
request.content = strio.read(MAX_MSG_SIZE)
y.yield request
request = Gitaly::WikiUpdatePageRequest.new
end
end
GitalyClient.call(@repository.storage, :wiki_service, :wiki_update_page, enum, timeout: GitalyClient.medium_timeout)
end
def find_page(title:, version: nil, dir: nil, load_content: true)
request = Gitaly::WikiFindPageRequest.new(
repository: @gitaly_repo,
title: encode_binary(title),
revision: encode_binary(version),
directory: encode_binary(dir),
skip_content: !load_content
)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request, timeout: GitalyClient.fast_timeout)
wiki_page_from_iterator(response)
end
def list_all_pages(limit: 0, sort: nil, direction_desc: false)
sort_value = Gitaly::WikiListPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
params[:sort] = sort_value if sort_value
request = Gitaly::WikiListPagesRequest.new(params)
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_list_pages, request, timeout: GitalyClient.medium_timeout)
stream.each_with_object([]) do |message, pages|
page = message.page
next unless page
wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = new_wiki_page_version(page.version)
pages << [wiki_page, version]
end
end
def load_all_pages(limit: 0, sort: nil, direction_desc: false)
sort_value = Gitaly::WikiGetAllPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
params[:sort] = sort_value if sort_value
request = Gitaly::WikiGetAllPagesRequest.new(params)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout)
pages = []
loop do
page, version = wiki_page_from_iterator(response) { |message| message.end_of_page }
break unless page && version
pages << [page, version]
end
pages
end
private
# If a block is given and the yielded value is truthy, iteration will be
# stopped early at that point; else the iterator is consumed entirely.
# The iterator is traversed with `next` to allow resuming the iteration.
def wiki_page_from_iterator(iterator)
wiki_page = version = nil
while message = iterator.next
break if block_given? && yield(message)
page = message.page
next unless page
if wiki_page
wiki_page.raw_data << page.raw_data
else
wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = new_wiki_page_version(page.version)
end
end
[wiki_page, version]
rescue StopIteration
[wiki_page, version]
end
def new_wiki_page_version(version)
Gitlab::Git::WikiPageVersion.new(
Gitlab::Git::Commit.decorate(@repository, version.commit),
version.format
)
end
def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new(
user_id: commit_details.user_id,
user_name: encode_binary(commit_details.username),
name: encode_binary(commit_details.name),
email: encode_binary(commit_details.email),
message: encode_binary(commit_details.message)
)
end
end
end
end

View File

@ -2,6 +2,9 @@
# A dumb middleware that returns a Go HTML document if the go-get=1 query string # A dumb middleware that returns a Go HTML document if the go-get=1 query string
# is used irrespective if the namespace/project exists # is used irrespective if the namespace/project exists
#
# In order to prevent the project from being exposed,
# auth failure (401 & 403) is regarded as not found (404)
module Gitlab module Gitlab
module Middleware module Middleware
class Go class Go
@ -21,15 +24,16 @@ module Gitlab
rescue Gitlab::Auth::IpBlacklisted rescue Gitlab::Auth::IpBlacklisted
Gitlab::AuthLogger.error( Gitlab::AuthLogger.error(
message: 'Rack_Attack', message: 'Rack_Attack',
status: 403,
env: :blocklist, env: :blocklist,
remote_ip: request.ip, remote_ip: request.ip,
request_method: request.request_method, request_method: request.request_method,
path: request.fullpath path: request.fullpath
) )
Rack::Response.new('', 403).finish Rack::Response.new('', 404).finish
rescue Gitlab::Auth::MissingPersonalAccessTokenError rescue Gitlab::Auth::MissingPersonalAccessTokenError
Rack::Response.new('', 401).finish Rack::Response.new('', 404).finish
rescue ActiveRecord::RecordNotFound
Rack::Response.new('', 404).finish
end end
private private
@ -100,7 +104,6 @@ module Gitlab
# We find all potential project paths out of the path segments # We find all potential project paths out of the path segments
path_segments = path.split('/') path_segments = path.split('/')
simple_project_path = path_segments.first(2).join('/')
project_paths = [] project_paths = []
begin begin
@ -110,28 +113,18 @@ module Gitlab
# We see if a project exists with any of these potential paths # We see if a project exists with any of these potential paths
project = project_for_paths(project_paths, request) project = project_for_paths(project_paths, request)
# If a project is found and the user has access, we return the full project path
if project [project.full_path, project.default_branch]
# If a project is found and the user has access, we return the full project path
[project.full_path, project.default_branch]
else
# If not, we return the first two components as if it were a simple `namespace/project` path,
# so that we don't reveal the existence of a nested project the user doesn't have access to.
# This means that for an unauthenticated request to `group/subgroup/project/subpackage`
# for a private `group/subgroup/project` with subpackage path `subpackage`, GitLab will respond
# as if the user is looking for project `group/subgroup`, with subpackage path `project/subpackage`.
# Since `go get` doesn't authenticate by default, this means that
# `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
# `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
# to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
[simple_project_path, 'master']
end
end end
def project_for_paths(paths, request) def project_for_paths(paths, request)
project = Project.where_full_path_in(paths).first project = Project.where_full_path_in(paths).first
return unless authentication_result(request, project).can_perform_action_on_project?(:read_project, project) raise ActiveRecord::RecordNotFound unless project
unless authentication_result(request, project).can_perform_action_on_project?(:read_project, project)
raise Gitlab::Auth::MissingPersonalAccessTokenError
end
project project
end end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'logger'
if Rails.env.development?
require 'grape-swagger/rake/oapi_tasks'
GrapeSwagger::Rake::OapiTasks.new('::API::API')
end
namespace :gitlab do
namespace :openapi do
task :generate do
raise 'This task can only be run in the development environment' unless Rails.env.development?
ENV['store'] = 'tmp/openapi.json'
Rake::Task["oapi:fetch"].invoke(['openapi.json'])
yaml_content = Gitlab::Json.parse(File.read('tmp/openapi_swagger_doc.json')).to_yaml
File.write("doc/api/openapi/openapi_v2.yaml", yaml_content)
end
end
end

View File

@ -2,10 +2,7 @@
module QA module QA
RSpec.describe 'Create' do RSpec.describe 'Create' do
describe 'Repository License Detection', product_group: :source_code, quarantine: { describe 'Repository License Detection', product_group: :source_code do
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/376154',
type: :stale
} do
after do after do
project.remove_via_api! project.remove_via_api!
end end

View File

@ -1,15 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :git_wiki_commit_details, class: 'Gitlab::Git::Wiki::CommitDetails' do
skip_create
transient do
author { association(:user) }
end
sequence(:message) { |n| "Commit message #{n}" }
initialize_with { new(author.id, author.username, author.name, author.email, message) }
end
end

View File

@ -12,37 +12,14 @@ RSpec.describe 'Project wikis', :js do
let(:wiki) { create(:project_wiki, user: user, project: project) } let(:wiki) { create(:project_wiki, user: user, project: project) }
let(:project) { create(:project, namespace: user.namespace, creator: user) } let(:project) { create(:project, namespace: user.namespace, creator: user) }
context 'with legacy wiki rpcs' do it_behaves_like 'User creates wiki page'
before do it_behaves_like 'User deletes wiki page'
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: false) it_behaves_like 'User previews wiki changes'
end it_behaves_like 'User updates wiki page'
it_behaves_like 'User uses wiki shortcuts'
it_behaves_like 'User creates wiki page' it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User deletes wiki page' it_behaves_like 'User views a wiki page'
it_behaves_like 'User previews wiki changes' it_behaves_like 'User views wiki pages'
it_behaves_like 'User updates wiki page' it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User uses wiki shortcuts' it_behaves_like 'User views Git access wiki page'
it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages'
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end
context 'with normal repository rpcs' do
before do
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: true)
end
it_behaves_like 'User creates wiki page'
it_behaves_like 'User deletes wiki page'
it_behaves_like 'User previews wiki changes'
it_behaves_like 'User updates wiki page'
it_behaves_like 'User uses wiki shortcuts'
it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages', false
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end
end end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Work item', :js do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:work_item) { create(:work_item, project: project) }
context 'for signed in user' do
before do
project.add_developer(user)
sign_in(user)
visit project_work_items_path(project, work_items_path: work_item.id)
end
context 'in work item description' do
it 'shows GFM autocomplete', :aggregate_failures do
click_button "Edit description"
find('[aria-label="Description"]').send_keys("@#{user.username}")
wait_for_requests
page.within('.atwho-container') do
expect(page).to have_text(user.name)
end
end
end
end
end

View File

@ -75,78 +75,38 @@ RSpec.describe WikiHelper do
describe '#wiki_sort_controls' do describe '#wiki_sort_controls' do
let(:wiki) { create(:project_wiki) } let(:wiki) { create(:project_wiki) }
let(:wiki_link) { helper.wiki_sort_controls(wiki, sort, direction) } let(:wiki_link) { helper.wiki_sort_controls(wiki, direction) }
let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" } let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
def expected_link(sort, direction, icon_class) def expected_link(direction, icon_class)
reversed_direction = direction == 'desc' ? 'asc' : 'desc' path = "/#{wiki.project.full_path}/-/wikis/pages?direction=#{direction}"
path = title = direction == 'desc' ? _('Sort direction: Ascending') : _('Sort direction: Descending')
if sort
"/#{wiki.project.full_path}/-/wikis/pages?direction=#{reversed_direction}&sort=#{sort}"
else
"/#{wiki.project.full_path}/-/wikis/pages?direction=#{reversed_direction}"
end
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
helper.link_to(path, type: 'button', class: classes, title: title) do helper.link_to(path, type: 'button', class: classes, title: title) do
helper.sprite_icon("sort-#{icon_class}") helper.sprite_icon("sort-#{icon_class}")
end end
end end
context 'wiki sorting enabled' do context 'initial call' do
before do let(:direction) { nil }
allow(wiki).to receive(:disable_sorting?).and_return(false)
end
context 'initial call' do it 'renders with default values' do
let(:sort) { nil } expect(wiki_link).to eq(expected_link('desc', 'lowest'))
let(:direction) { nil }
it 'renders with default values' do
expect(wiki_link).to eq(expected_link('title', 'asc', 'lowest'))
end
end
context 'sort by title' do
let(:sort) { 'title' }
let(:direction) { 'asc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('title', 'aesc', 'lowest'))
end
end
context 'sort by created_at' do
let(:sort) { 'created_at' }
let(:direction) { 'desc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('created_at', 'desc', 'highest'))
end
end end
end end
context 'wiki sorting disabled' do context 'sort by asc order' do
before do let(:direction) { 'asc' }
allow(wiki).to receive(:disable_sorting?).and_return(true)
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
end end
end
context 'sort by created_at' do context 'sort by desc order' do
let(:sort) { 'created_at' } let(:direction) { 'desc' }
let(:direction) { 'asc' }
it 'ignores created_at and renders a link with opposite direction' do it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link(nil, 'asc', 'lowest')) expect(wiki_link).to eq(expected_link('asc', 'highest'))
end
end
context 'initial call' do
let(:sort) { nil }
let(:direction) { nil }
it 'renders with default values' do
expect(wiki_link).to eq(expected_link(nil, 'asc', 'lowest'))
end
end end
end end
end end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Helpers::OpenApi do
describe 'class methods' do
let(:klass) { Class.new.include(described_class) }
describe '.add_open_api_documentation!' do
before do
allow(YAML).to receive(:load_file).and_return({ 'metadata' => { 'key' => 'value' } })
end
it 'calls the add_swagger_documentation method' do
expect(klass).to receive(:add_swagger_documentation).with({ key: 'value' })
klass.add_open_api_documentation!
end
end
end
end

View File

@ -1,134 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Git::Wiki do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
let(:user) { project.first_owner }
let(:project_wiki) { ProjectWiki.new(project, user) }
let(:repository) { project_wiki.repository }
let(:default_branch) { described_class.default_ref(project) }
subject(:wiki) { project_wiki.wiki }
before do
repository.create_if_not_exists(project_wiki.default_branch)
end
describe '#pages' do
before do
create_page('page1', 'content')
create_page('page2', 'content2')
end
after do
destroy_page('page1')
destroy_page('page2')
end
it 'returns all the pages' do
expect(subject.list_pages.count).to eq(2)
expect(subject.list_pages.first.title).to eq 'page1'
expect(subject.list_pages.last.title).to eq 'page2'
end
it 'returns only one page' do
pages = subject.list_pages(limit: 1)
expect(pages.count).to eq(1)
expect(pages.first.title).to eq 'page1'
end
end
describe '#page' do
before do
create_page('page1', 'content')
create_page('foo/page1', 'content foo/page1')
end
after do
destroy_page('page1')
destroy_page('foo/page1')
end
it 'returns the right page' do
page = subject.page(title: 'page1', dir: '')
expect(page.url_path).to eq 'page1'
expect(page.raw_data).to eq 'content'
page = subject.page(title: 'page1', dir: 'foo')
expect(page.url_path).to eq 'foo/page1'
expect(page.raw_data).to eq 'content foo/page1'
end
it 'returns nil for invalid arguments' do
expect(subject.page(title: '')).to be_nil
expect(subject.page(title: 'foo', version: ':')).to be_nil
end
it 'does not return content if load_content param is set to false' do
page = subject.page(title: 'page1', dir: '', load_content: false)
expect(page.url_path).to eq 'page1'
expect(page.raw_data).to be_empty
end
end
describe '#preview_slug' do
where(:title, :file_extension, :format, :expected_slug) do
'The Best Thing' | :md | :markdown | 'The-Best-Thing'
'The Best Thing' | :md | :md | 'The-Best-Thing'
'The Best Thing' | :txt | :txt | 'The-Best-Thing'
'A Subject/Title Here' | :txt | :txt | 'A-Subject/Title-Here'
'A subject' | :txt | :txt | 'A-subject'
'A 1/B 2/C 3' | :txt | :txt | 'A-1/B-2/C-3'
'subject/title' | :txt | :txt | 'subject/title'
'subject/title.md' | :txt | :txt | 'subject/title.md'
'foo<bar>+baz' | :txt | :txt | 'foo-bar--baz'
'foo%2Fbar' | :txt | :txt | 'foo%2Fbar'
'' | :md | :markdown | '.md'
'' | :md | :md | '.md'
'' | :txt | :txt | '.txt'
end
with_them do
subject { wiki.preview_slug(title, format) }
let(:gitaly_slug) { wiki.list_pages.first }
it { is_expected.to eq(expected_slug) }
it 'matches the slug generated by gitaly' do
skip('Gitaly cannot generate a slug for an empty title') unless title.present?
create_page(title, 'content', file_extension)
gitaly_slug = wiki.list_pages.first.url_path
is_expected.to eq(gitaly_slug)
end
end
end
def create_page(name, content, extension = :md)
repository.create_file(
user, ::Wiki.sluggified_full_path(name, extension.to_s), content,
branch_name: default_branch,
message: "created page #{name}",
author_email: user.email,
author_name: user.name
)
end
def destroy_page(name, extension = :md)
repository.delete_file(
user, ::Wiki.sluggified_full_path(name, extension.to_s),
branch_name: described_class.default_ref(project),
message: "delete page #{name}",
author_email: user.email,
author_name: user.name
)
end
end

View File

@ -1,118 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GitalyClient::WikiService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
let(:client) { described_class.new(project.repository) }
let(:commit) { create(:gitaly_commit) }
let(:page_version) { Gitaly::WikiPageVersion.new(format: 'markdown', commit: commit) }
let(:page_info) { { title: 'My Page', raw_data: 'a', version: page_version } }
describe '#find_page' do
let(:response) do
[
Gitaly::WikiFindPageResponse.new(page: Gitaly::WikiPage.new(page_info)),
Gitaly::WikiFindPageResponse.new(page: Gitaly::WikiPage.new(raw_data: 'b'))
]
end
let(:wiki_page) { subject.first }
let(:wiki_page_version) { subject.last }
subject { client.find_page(title: 'My Page', version: 'master', dir: '') }
it 'sends a wiki_find_page message' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_find_page)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([].each)
subject
end
it 'concatenates the raw data and returns a pair of WikiPage and WikiPageVersion' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_find_page)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(response.each)
expect(wiki_page.title).to eq('My Page')
expect(wiki_page.raw_data).to eq('ab')
expect(wiki_page_version.format).to eq('markdown')
expect(wiki_page.title).to be_utf8
expect(wiki_page.path).to be_utf8
expect(wiki_page.name).to be_utf8
end
end
describe '#load_all_pages' do
let(:page_2_info) { { title: 'My Page 2', raw_data: 'c', version: page_version } }
let(:response) do
[
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(page_info)),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(raw_data: 'b')),
Gitaly::WikiGetAllPagesResponse.new(end_of_page: true),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(page_2_info)),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(raw_data: 'd')),
Gitaly::WikiGetAllPagesResponse.new(end_of_page: true)
]
end
let(:wiki_page_1) { subject[0].first }
let(:wiki_page_1_version) { subject[0].last }
let(:wiki_page_2) { subject[1].first }
let(:wiki_page_2_version) { subject[1].last }
subject { client.load_all_pages }
it 'sends a wiki_get_all_pages message' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([].each)
subject
end
it 'sends a limit of 0 to wiki_get_all_pages' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_params(limit: 0), kind_of(Hash))
.and_return([].each)
subject
end
it 'concatenates the raw data and returns a pair of WikiPage and WikiPageVersion for each page' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(response.each)
expect(subject.size).to be(2)
expect(wiki_page_1.title).to eq('My Page')
expect(wiki_page_1.raw_data).to eq('ab')
expect(wiki_page_1_version.format).to eq('markdown')
expect(wiki_page_2.title).to eq('My Page 2')
expect(wiki_page_2.raw_data).to eq('cd')
expect(wiki_page_2_version.format).to eq('markdown')
end
context 'with limits' do
subject { client.load_all_pages(limit: 1) }
it 'sends a request with the limit' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_params(limit: 1), kind_of(Hash))
.and_return([].each)
subject
end
end
end
end

View File

@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
allow_next_instance_of(Gitlab::ImportExport) do |instance| allow_next_instance_of(Gitlab::ImportExport) do |instance|
allow(instance).to receive(:storage_path).and_return(export_path) allow(instance).to receive(:storage_path).and_return(export_path)
end end
project_wiki.wiki project_wiki.create_wiki_repository
project_wiki.create_page("index", "test content") project_wiki.create_page("index", "test content")
end end

View File

@ -32,7 +32,7 @@ RSpec.describe Gitlab::Middleware::Go do
shared_examples 'go-get=1' do |enabled_protocol:| shared_examples 'go-get=1' do |enabled_protocol:|
context 'with simple 2-segment project path' do context 'with simple 2-segment project path' do
let!(:project) { create(:project, :private, :repository) } let!(:project) { create(:project, :public, :repository) }
context 'with subpackages' do context 'with subpackages' do
let(:path) { "#{project.full_path}/subpackage" } let(:path) { "#{project.full_path}/subpackage" }
@ -51,6 +51,16 @@ RSpec.describe Gitlab::Middleware::Go do
end end
end end
context 'with nonexistent path' do
let(:path) { 'nonexistent-group/nonexistent-project' }
it 'responses not found' do
status_code, _headers, body = go
expect(status_code).to eq(404)
expect(body).to match_array([''])
end
end
context 'with a nested project path' do context 'with a nested project path' do
let(:group) { create(:group, :nested) } let(:group) { create(:group, :nested) }
let!(:project) { create(:project, :public, :repository, namespace: group) } let!(:project) { create(:project, :public, :repository, namespace: group) }
@ -68,8 +78,10 @@ RSpec.describe Gitlab::Middleware::Go do
end end
shared_examples 'unauthorized' do shared_examples 'unauthorized' do
it 'returns the 2-segment group path' do it 'returns unauthorized' do
expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch) status_code, _headers, body = go
expect(status_code).to eq(404)
expect(body).to match_array([''])
end end
end end
@ -141,7 +153,7 @@ RSpec.describe Gitlab::Middleware::Go do
expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlacklisted) expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlacklisted)
response = go response = go
expect(response[0]).to eq(403) expect(response[0]).to eq(404)
expect(response[1]['Content-Length']).to be_nil expect(response[1]['Content-Length']).to be_nil
expect(response[2]).to eq(['']) expect(response[2]).to eq([''])
end end
@ -158,7 +170,7 @@ RSpec.describe Gitlab::Middleware::Go do
expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::MissingPersonalAccessTokenError) expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::MissingPersonalAccessTokenError)
response = go response = go
expect(response[0]).to eq(401) expect(response[0]).to eq(404)
expect(response[1]['Content-Length']).to be_nil expect(response[1]['Content-Length']).to be_nil
expect(response[2]).to eq(['']) expect(response[2]).to eq([''])
end end

View File

@ -24,8 +24,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
context 'migration to instrumentation classes data collection' do context 'migration to instrumentation classes data collection' do
let_it_be(:instrumented_events) do let_it_be(:instrumented_events) do
instrumentation_classes = %w[AggregatedMetric RedisHLLMetric]
::Gitlab::Usage::MetricDefinition.all.map do |definition| ::Gitlab::Usage::MetricDefinition.all.map do |definition|
next unless definition.attributes[:instrumentation_class] == 'RedisHLLMetric' && definition.available? next unless definition.available?
next unless instrumentation_classes.include?(definition.attributes[:instrumentation_class])
definition.attributes.dig(:options, :events)&.sort definition.attributes.dig(:options, :events)&.sort
end.compact.to_set end.compact.to_set

View File

@ -340,7 +340,7 @@ RSpec.describe Projects::UpdateService do
it 'logs an error and creates a metric when wiki can not be created' do it 'logs an error and creates a metric when wiki can not be created' do
project.project_feature.update!(wiki_access_level: ProjectFeature::DISABLED) project.project_feature.update!(wiki_access_level: ProjectFeature::DISABLED)
expect_any_instance_of(ProjectWiki).to receive(:wiki).and_raise(Wiki::CouldNotCreateWikiError) expect_any_instance_of(ProjectWiki).to receive(:create_wiki_repository).and_raise(Wiki::CouldNotCreateWikiError)
expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}") expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}")
counter = double(:counter) counter = double(:counter)

View File

@ -55,6 +55,7 @@
- Security::FindingsFinder - Security::FindingsFinder
- Security::PipelineVulnerabilitiesFinder - Security::PipelineVulnerabilitiesFinder
- Security::ScanExecutionPoliciesFinder - Security::ScanExecutionPoliciesFinder
- Security::ScanResultPoliciesFinder
- Security::TrainingProviders::BaseUrlFinder - Security::TrainingProviders::BaseUrlFinder
- Security::TrainingUrlsFinder - Security::TrainingUrlsFinder
- Security::TrainingProviders::KontraUrlFinder - Security::TrainingProviders::KontraUrlFinder

View File

@ -48,7 +48,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the wiki repository cannot be created' do context 'when the wiki repository cannot be created' do
before do before do
expect(Wiki).to receive(:for_container).and_return(wiki) expect(Wiki).to receive(:for_container).and_return(wiki)
expect(wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError } expect(wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end end
it 'redirects to the wiki container and displays an error message' do it 'redirects to the wiki container and displays an error message' do
@ -200,7 +200,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'the sidebar fails to load' do context 'the sidebar fails to load' do
before do before do
allow(Wiki).to receive(:for_container).and_return(wiki) allow(Wiki).to receive(:for_container).and_return(wiki)
wiki.wiki wiki.create_wiki_repository
expect(wiki).to receive(:find_sidebar) do expect(wiki).to receive(:find_sidebar) do
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded' raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
end end

View File

@ -4,7 +4,7 @@
# wiki # wiki
# user # user
RSpec.shared_examples 'User views wiki pages' do |support_sorting_by_created_at = true| RSpec.shared_examples 'User views wiki pages' do
include WikiHelpers include WikiHelpers
let!(:wiki_page1) do let!(:wiki_page1) do
@ -53,40 +53,4 @@ RSpec.shared_examples 'User views wiki pages' do |support_sorting_by_created_at
end end
end end
end end
if support_sorting_by_created_at
context 'ordered by created_at' do
let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] }
before do
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: false)
page.within('.wiki-sort-dropdown') do
click_button('Title')
click_button('Created date')
end
end
context 'asc' do
it 'pages are displayed in direct order' do
pages.each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
page.find('.rspec-reverse-sort').click
end
end
it 'pages are displayed in reversed order' do
pages.reverse_each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
end
end
end end

View File

@ -15,7 +15,7 @@ RSpec.shared_examples 'model with wiki' do
context 'when the repository cannot be created' do context 'when the repository cannot be created' do
before do before do
expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError } expect(container.wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end end
it 'returns false and adds a validation error' do it 'returns false and adds a validation error' do

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'wiki model' do RSpec.shared_examples 'wiki model' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user, :commit_email) } let_it_be(:user) { create(:user, :commit_email) }
let(:wiki_container) { raise NotImplementedError } let(:wiki_container) { raise NotImplementedError }
@ -124,36 +126,6 @@ RSpec.shared_examples 'wiki model' do
end end
end end
describe '#wiki' do
it 'contains a Gitlab::Git::Wiki instance' do
expect(subject.wiki).to be_a Gitlab::Git::Wiki
end
it 'creates a new wiki repo if one does not yet exist' do
expect(subject.create_page('index', 'test content')).to be_truthy
end
it 'creates a new wiki repo with a default commit message' do
expect(subject.create_page('index', 'test content', :markdown, '')).to be_truthy
page = subject.find_page('index')
expect(page.last_version.message).to eq("#{user.username} created page: index")
end
context 'when the repository cannot be created' do
let(:wiki_container) { wiki_container_without_repo }
before do
expect(subject.repository).to receive(:create_if_not_exists) { false }
end
it 'raises CouldNotCreateWikiError' do
expect { subject.wiki }.to raise_exception(Wiki::CouldNotCreateWikiError)
end
end
end
describe '#empty?' do describe '#empty?' do
context 'when the wiki repository is empty' do context 'when the wiki repository is empty' do
it 'returns true' do it 'returns true' do
@ -447,14 +419,6 @@ RSpec.shared_examples 'wiki model' do
end end
end end
context 'find page with legacy wiki service' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
it_behaves_like 'wiki model #find_page'
end
context 'find page with normal repository RPCs' do context 'find page with normal repository RPCs' do
it_behaves_like 'wiki model #find_page' it_behaves_like 'wiki model #find_page'
end end
@ -473,14 +437,6 @@ RSpec.shared_examples 'wiki model' do
end end
end end
context 'find sidebar with legacy wiki service' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
it_behaves_like 'wiki model #find_sidebar'
end
context 'find sidebar with normal repository RPCs' do context 'find sidebar with normal repository RPCs' do
it_behaves_like 'wiki model #find_sidebar' it_behaves_like 'wiki model #find_sidebar'
end end
@ -490,7 +446,7 @@ RSpec.shared_examples 'wiki model' do
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) } let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
before do before do
subject.wiki # Make sure the wiki repo exists subject.create_wiki_repository # Make sure the wiki repo exists
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image') subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end end
@ -694,14 +650,6 @@ RSpec.shared_examples 'wiki model' do
end end
it_behaves_like 'create_page tests' it_behaves_like 'create_page tests'
context 'create page with legacy find_page wiki service' do
it_behaves_like 'create_page tests' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
end
end
end end
describe '#update_page' do describe '#update_page' do
@ -800,17 +748,6 @@ RSpec.shared_examples 'wiki model' do
include_context 'extended examples' include_context 'extended examples'
end end
context 'update page with legacy find_page wiki service' do
it_behaves_like 'update_page tests' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
include_context 'common examples'
include_context 'extended examples'
end
end
context 'when format is invalid' do context 'when format is invalid' do
let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') } let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
@ -984,4 +921,40 @@ RSpec.shared_examples 'wiki model' do
end end
end end
end end
describe '#preview_slug' do
where(:title, :file_extension, :format, :expected_slug) do
'The Best Thing' | :md | :markdown | 'The-Best-Thing'
'The Best Thing' | :txt | :plaintext | 'The-Best-Thing'
'A Subject/Title Here' | :txt | :plaintext | 'A-Subject/Title-Here'
'A subject' | :txt | :plaintext | 'A-subject'
'A 1/B 2/C 3' | :txt | :plaintext | 'A-1/B-2/C-3'
'subject/title' | :txt | :plaintext | 'subject/title'
'subject/title.md' | :txt | :plaintext | 'subject/title.md'
'foo%2Fbar' | :txt | :plaintext | 'foo%2Fbar'
'' | :md | :markdown | '.md'
'' | :txt | :plaintext | '.txt'
end
with_them do
before do
subject.repository.create_file(
user, "#{title}.#{file_extension}", 'content',
branch_name: subject.default_branch,
message: "Add #{title}"
)
end
it do
expect(described_class.preview_slug(title, file_extension)).to eq(expected_slug)
end
it 'matches the slug generated by gitaly' do
skip('Gitaly cannot generate a slug for an empty title') unless title.present?
gitaly_slug = subject.list_pages.first.slug
expect(described_class.preview_slug(title, file_extension)).to eq(gitaly_slug)
end
end
end
end end