Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e761659df2
commit
efcfe56681
2
Gemfile
2
Gemfile
|
@ -119,6 +119,8 @@ gem 'net-ldap', '~> 0.16.3'
|
|||
gem 'grape', '~> 1.5.2'
|
||||
gem 'grape-entity', '~> 0.10.0'
|
||||
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
|
||||
gem 'graphql', '~> 1.13.12'
|
||||
|
|
|
@ -231,6 +231,8 @@
|
|||
{"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"},
|
||||
{"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-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":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"},
|
||||
{"name":"graphlient","version":"0.5.0","platform":"ruby","checksum":"0f2c9416142e50b6bd4edcd86fe6810f792951732c487f9061aee6d420e0f292"},
|
||||
|
|
|
@ -642,6 +642,11 @@ GEM
|
|||
grape (~> 1.3)
|
||||
rake (> 12)
|
||||
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
|
||||
rack
|
||||
|
@ -1638,6 +1643,8 @@ DEPENDENCIES
|
|||
grape (~> 1.5.2)
|
||||
grape-entity (~> 0.10.0)
|
||||
grape-path-helpers (~> 1.7.1)
|
||||
grape-swagger (~> 1.5.0)
|
||||
grape-swagger-entity (~> 0.5.1)
|
||||
grape_logging (~> 1.8)
|
||||
graphiql-rails (~> 1.8)
|
||||
graphlient (~> 0.5.0)
|
||||
|
|
|
@ -190,7 +190,10 @@ export default {
|
|||
<div class="media" :class="{ 'gl-cursor-pointer': isCollapsible }" @click="toggleCollapsed">
|
||||
<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 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">
|
||||
<p class="gl-line-height-normal gl-m-0">{{ headerText }}</p>
|
||||
<slot :name="slotName"></slot>
|
||||
|
|
|
@ -209,9 +209,7 @@ module WikiActions
|
|||
def wiki
|
||||
strong_memoize(:wiki) do
|
||||
wiki = Wiki.for_container(container, current_user)
|
||||
|
||||
# Call #wiki to make sure the Wiki Repo is initialized
|
||||
wiki.wiki
|
||||
wiki.create_wiki_repository
|
||||
|
||||
wiki
|
||||
end
|
||||
|
@ -242,7 +240,7 @@ module WikiActions
|
|||
def wiki_pages
|
||||
strong_memoize(:wiki_pages) do
|
||||
Kaminari.paginate_array(
|
||||
wiki.list_pages(sort: params[:sort], direction: params[:direction])
|
||||
wiki.list_pages(direction: params[:direction])
|
||||
).page(params[:page])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,15 +59,13 @@ module WikiHelper
|
|||
end
|
||||
end
|
||||
|
||||
def wiki_sort_controls(wiki, sort, direction)
|
||||
sort ||= Wiki::TITLE_ORDER
|
||||
def wiki_sort_controls(wiki, direction)
|
||||
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'
|
||||
icon_class = direction == 'desc' ? 'highest' : 'lowest'
|
||||
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
|
||||
|
||||
link_options = { action: :pages, direction: reversed_direction }
|
||||
link_options[:sort] = sort unless wiki.disable_sorting?
|
||||
|
||||
link_to(wiki_path(wiki, **link_options),
|
||||
type: 'button', class: link_class, title: title) do
|
||||
|
|
|
@ -8,7 +8,7 @@ module HasWiki
|
|||
end
|
||||
|
||||
def create_wiki
|
||||
wiki.wiki
|
||||
wiki.create_wiki_repository
|
||||
true
|
||||
rescue Wiki::CouldNotCreateWikiError
|
||||
errors.add(:base, _('Failed to create wiki'))
|
||||
|
|
|
@ -9,6 +9,8 @@ class Wiki
|
|||
|
||||
extend ActiveModel::Naming
|
||||
|
||||
DuplicatePageError = Class.new(StandardError)
|
||||
|
||||
MARKUPS = { # rubocop:disable Style/MultilineIfModifier
|
||||
markdown: {
|
||||
name: 'Markdown',
|
||||
|
@ -114,6 +116,29 @@ class Wiki
|
|||
title = Pathname.new(title).relative_path_from('/').to_s
|
||||
title.tr(' ', '-')
|
||||
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
|
||||
|
||||
def initialize(container, user = nil)
|
||||
|
@ -145,14 +170,6 @@ class Wiki
|
|||
container.path + '.wiki'
|
||||
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
|
||||
repository.create_if_not_exists(default_branch)
|
||||
|
||||
|
@ -190,13 +207,9 @@ class Wiki
|
|||
#
|
||||
# Returns an Array of GitLab WikiPage instances or an
|
||||
# empty Array if this Wiki has no pages.
|
||||
def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false)
|
||||
if list_pages_with_repository_rpcs?
|
||||
def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false)
|
||||
create_wiki_repository unless repository_exists?
|
||||
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
|
||||
list_pages_with_repository_rpcs(limit: limit, direction: direction, load_content: load_content)
|
||||
end
|
||||
|
||||
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
|
||||
|
@ -215,12 +228,8 @@ class Wiki
|
|||
#
|
||||
# Returns an initialized WikiPage instance or nil
|
||||
def find_page(title, version = nil, load_content: true)
|
||||
if find_page_with_repository_rpcs?
|
||||
create_wiki_repository unless repository_exists?
|
||||
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
|
||||
|
||||
def find_sidebar(version = nil)
|
||||
|
@ -254,7 +263,7 @@ class Wiki
|
|||
raise_duplicate_page_error!
|
||||
end
|
||||
end
|
||||
rescue Gitlab::Git::Wiki::DuplicatePageError => e
|
||||
rescue DuplicatePageError => e
|
||||
@error_message = _("Duplicate page: %{error_message}" % { error_message: e.message })
|
||||
|
||||
false
|
||||
|
@ -270,6 +279,7 @@ class Wiki
|
|||
extension = page.format != format.to_sym ? default_extension : File.extname(page.path).downcase[1..]
|
||||
|
||||
capture_git_error(:updated) do
|
||||
create_wiki_repository unless repository_exists?
|
||||
repository.update_file(
|
||||
user,
|
||||
sluggified_full_path(title, extension),
|
||||
|
@ -288,6 +298,7 @@ class Wiki
|
|||
return unless page
|
||||
|
||||
capture_git_error(:deleted) do
|
||||
create_wiki_repository unless repository_exists?
|
||||
repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title))
|
||||
|
||||
after_wiki_activity
|
||||
|
@ -304,8 +315,10 @@ class Wiki
|
|||
[title, title_array.join("/")]
|
||||
end
|
||||
|
||||
# TODO: This method is redundant. Should be replaced by create_wiki_repository
|
||||
def ensure_repository
|
||||
raise CouldNotCreateWikiError unless wiki.repository_exists?
|
||||
create_wiki_repository
|
||||
raise CouldNotCreateWikiError unless repository_exists?
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
|
@ -341,7 +354,7 @@ class Wiki
|
|||
|
||||
override :default_branch
|
||||
def default_branch
|
||||
super || Gitlab::Git::Wiki.default_ref(container)
|
||||
super || Gitlab::DefaultBranch.value(object: container)
|
||||
end
|
||||
|
||||
def wiki_base_path
|
||||
|
@ -381,10 +394,6 @@ class Wiki
|
|||
false
|
||||
end
|
||||
|
||||
def disable_sorting?
|
||||
list_pages_with_repository_rpcs?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def multi_commit_options(action, message = nil, title = nil)
|
||||
|
@ -429,7 +438,7 @@ class Wiki
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def sluggified_full_path(title, extension)
|
||||
|
@ -441,15 +450,7 @@ class Wiki
|
|||
end
|
||||
|
||||
def canonicalize_filename(filename)
|
||||
Gitlab::Git::Wiki::GollumSlug.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
|
||||
self.class.canonicalize_filename(filename)
|
||||
end
|
||||
|
||||
def find_matched_file(title, version)
|
||||
|
@ -494,17 +495,6 @@ class Wiki
|
|||
WikiPage.new(self, page)
|
||||
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
|
||||
# We could not use ALLOWED_EXTENSIONS_REGEX constant or similar regexp with
|
||||
# Regexp.union. The result combination complicated modifiers:
|
||||
|
@ -519,17 +509,6 @@ class Wiki
|
|||
path.sub(/\.[^.]+\z/, "")
|
||||
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
|
||||
return [] if repository.empty?
|
||||
|
||||
|
@ -537,7 +516,7 @@ class Wiki
|
|||
repository.search_files_by_regexp(path_regexp, default_branch)
|
||||
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
|
||||
return [] if paths.empty?
|
||||
|
||||
|
@ -553,29 +532,18 @@ class Wiki
|
|||
)
|
||||
WikiPage.new(self, page)
|
||||
end
|
||||
sort_pages!(pages, sort, direction)
|
||||
sort_pages!(pages, direction)
|
||||
pages = pages.take(limit) if limit > 0
|
||||
fetch_pages_content!(pages) if load_content
|
||||
|
||||
pages
|
||||
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
|
||||
# pages by created_at. We have to either ListLastCommitsForTree RPC call or
|
||||
# N+1 LastCommitForPath. Either are efficient for a large repository.
|
||||
# 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
|
||||
pages.sort_by!(&:path)
|
||||
pages.reverse! if direction == DIRECTION_DESC
|
||||
|
|
|
@ -73,7 +73,7 @@ class WikiPage
|
|||
|
||||
# The escaped URL path of this page.
|
||||
def slug
|
||||
attributes[:slug].presence || wiki.wiki.preview_slug(title, format)
|
||||
attributes[:slug].presence || ::Wiki.preview_slug(title, format)
|
||||
end
|
||||
alias_method :id, :slug # required to use build_stubbed
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
- page_title s_('WorkItem|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) }
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
%div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' }
|
||||
%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
|
||||
%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
|
||||
= simple_search_highlight_and_truncate(wiki_blob.data, @search_term)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
- breadcrumb_title s_("Wiki|Pages")
|
||||
- page_title s_("Wiki|Pages"), _("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
|
||||
%h1.page-title.gl-font-size-h-display.gl-flex-grow-1
|
||||
|
@ -15,9 +14,7 @@
|
|||
|
||||
.dropdown.inline.wiki-sort-dropdown
|
||||
.btn-group{ role: 'group' }
|
||||
- unless @wiki.disable_sorting?
|
||||
= gl_redirect_listbox_tag wiki_sort_options, params[:sort], data: { right: true }
|
||||
= wiki_sort_controls(@wiki, params[:sort], params[:direction])
|
||||
= wiki_sort_controls(@wiki, params[:direction])
|
||||
|
||||
%ul.wiki-pages-list.content-list
|
||||
= render @wiki_entries, context: 'pages'
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -10,8 +10,11 @@ value_type: number
|
|||
status: active
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
|
|
|
@ -10,8 +10,11 @@ value_type: number
|
|||
status: active
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- g_project_management_issue_title_changed
|
||||
- g_project_management_issue_description_changed
|
||||
|
|
|
@ -12,8 +12,11 @@ milestone: "13.12"
|
|||
introduced_by_url:
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- o_pipeline_authoring_unique_users_committing_ciconfigfile
|
||||
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile
|
||||
|
|
|
@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
|
|||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- error_tracking_view_list
|
||||
- error_tracking_view_details
|
||||
|
|
|
@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
|
|||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- incident_management_timeline_event_created
|
||||
- incident_management_timeline_event_edited
|
||||
|
|
|
@ -10,8 +10,11 @@ value_type: number
|
|||
status: active
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
|
|
|
@ -10,8 +10,11 @@ value_type: number
|
|||
status: active
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- g_project_management_issue_title_changed
|
||||
- g_project_management_issue_description_changed
|
||||
|
|
|
@ -12,8 +12,11 @@ milestone: "13.12"
|
|||
introduced_by_url:
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- o_pipeline_authoring_unique_users_committing_ciconfigfile
|
||||
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile
|
||||
|
|
|
@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
|
|||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- error_tracking_view_list
|
||||
- error_tracking_view_details
|
||||
|
|
|
@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
|
|||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
instrumentation_class: AggregatedMetric
|
||||
options:
|
||||
aggregate:
|
||||
operator: OR
|
||||
attribute: user_id
|
||||
events:
|
||||
- incident_management_timeline_event_created
|
||||
- incident_management_timeline_event_edited
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -50,7 +50,7 @@ For example, consider a migration that creates a table with two text columns,
|
|||
`db/migrate/20200401000001_create_db_guides.rb`:
|
||||
|
||||
```ruby
|
||||
class CreateDbGuides < Gitlab::Database::Migration[1.0]
|
||||
class CreateDbGuides < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
create_table :db_guides do |t|
|
||||
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`:
|
||||
|
||||
```ruby
|
||||
class AddExtendedTitleToSprints < Gitlab::Database::Migration[1.0]
|
||||
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.0]
|
||||
|
||||
# rubocop:disable Migration/AddLimitToTextColumns
|
||||
# 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`:
|
||||
|
||||
```ruby
|
||||
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[1.0]
|
||||
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
|
@ -165,7 +165,7 @@ in a post-deployment migration,
|
|||
`db/post_migrate/20200501000001_add_text_limit_migration.rb`:
|
||||
|
||||
```ruby
|
||||
class AddTextLimitMigration < Gitlab::Database::Migration[1.0]
|
||||
class AddTextLimitMigration < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
|
@ -236,7 +236,7 @@ helper in a final post-deployment migration,
|
|||
`db/post_migrate/20200601000001_validate_text_limit_migration.rb`:
|
||||
|
||||
```ruby
|
||||
class ValidateTextLimitMigration < Gitlab::Database::Migration[1.0]
|
||||
class ValidateTextLimitMigration < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
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:
|
||||
|
||||
```ruby
|
||||
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[1.0]
|
||||
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
|
|
|
@ -139,5 +139,5 @@ end
|
|||
```
|
||||
|
||||
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
|
||||
rolled back in case something goes wrong. They act as 3rd part calls.
|
||||
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 third-party calls.
|
||||
|
|
|
@ -9,6 +9,10 @@ comments: false
|
|||
|
||||
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
|
||||
|
||||
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).
|
||||
- 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
|
||||
|
||||
### SSL certificate errors
|
||||
|
|
|
@ -152,6 +152,8 @@ You need at least the Developer role to edit a wiki page:
|
|||
1. Edit the content.
|
||||
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
|
||||
|
||||
To generate a table of contents from a wiki page's subheadings, use the `[[_TOC_]]` tag.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module API
|
||||
class API < ::API::Base
|
||||
include APIGuard
|
||||
include Helpers::OpenApi
|
||||
|
||||
LOG_FILENAME = Rails.root.join("log", "api_json.log")
|
||||
|
||||
|
@ -165,6 +166,13 @@ module API
|
|||
::Users::ActivityService.new(@current_user).execute
|
||||
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
|
||||
mount ::API::AccessRequests
|
||||
mount ::API::Admin::BatchedBackgroundMigrations
|
||||
|
@ -250,7 +258,6 @@ module API
|
|||
mount ::API::MergeRequestApprovals
|
||||
mount ::API::MergeRequestDiffs
|
||||
mount ::API::MergeRequests
|
||||
mount ::API::Metadata
|
||||
mount ::API::Metrics::Dashboard::Annotations
|
||||
mount ::API::Metrics::UserStarredDashboards
|
||||
mount ::API::Namespaces
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -35,8 +35,30 @@ module API
|
|||
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.'
|
||||
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
|
||||
get '/metadata' do
|
||||
run_metadata_query
|
||||
|
@ -47,7 +69,30 @@ module API
|
|||
desc 'Get the version information of the GitLab instance.' do
|
||||
detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \
|
||||
'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
|
||||
|
||||
get '/version' do
|
||||
run_metadata_query
|
||||
end
|
||||
|
|
|
@ -33,3 +33,8 @@ test:
|
|||
stage: test
|
||||
script:
|
||||
- ./runmytests.sh
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -28,3 +28,8 @@ test:
|
|||
# If you need to run any migrations or configure the database, this
|
||||
# would be the point to do it.
|
||||
- lein test
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -42,3 +42,8 @@ spec:
|
|||
minitest:
|
||||
script:
|
||||
- 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
|
||||
|
|
|
@ -74,3 +74,8 @@ django-tests:
|
|||
- 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
|
||||
- python3 manage.py test
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -111,3 +111,8 @@ tests:
|
|||
# (e.g. integration tests, unit tests etc).
|
||||
script:
|
||||
- 'dotnet test --no-restore'
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -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
|
|
@ -5,22 +5,6 @@ module Gitlab
|
|||
class WikiPage
|
||||
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)
|
||||
@url_path = hash[:url_path]
|
||||
@title = hash[:title]
|
||||
|
|
|
@ -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
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
# A dumb middleware that returns a Go HTML document if the go-get=1 query string
|
||||
# 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 Middleware
|
||||
class Go
|
||||
|
@ -21,15 +24,16 @@ module Gitlab
|
|||
rescue Gitlab::Auth::IpBlacklisted
|
||||
Gitlab::AuthLogger.error(
|
||||
message: 'Rack_Attack',
|
||||
status: 403,
|
||||
env: :blocklist,
|
||||
remote_ip: request.ip,
|
||||
request_method: request.request_method,
|
||||
path: request.fullpath
|
||||
)
|
||||
Rack::Response.new('', 403).finish
|
||||
Rack::Response.new('', 404).finish
|
||||
rescue Gitlab::Auth::MissingPersonalAccessTokenError
|
||||
Rack::Response.new('', 401).finish
|
||||
Rack::Response.new('', 404).finish
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Rack::Response.new('', 404).finish
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -100,7 +104,6 @@ module Gitlab
|
|||
|
||||
# We find all potential project paths out of the path segments
|
||||
path_segments = path.split('/')
|
||||
simple_project_path = path_segments.first(2).join('/')
|
||||
|
||||
project_paths = []
|
||||
begin
|
||||
|
@ -110,28 +113,18 @@ module Gitlab
|
|||
|
||||
# We see if a project exists with any of these potential paths
|
||||
project = project_for_paths(project_paths, request)
|
||||
|
||||
if project
|
||||
# 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
|
||||
|
||||
def project_for_paths(paths, request)
|
||||
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
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Create' do
|
||||
describe 'Repository License Detection', product_group: :source_code, quarantine: {
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/376154',
|
||||
type: :stale
|
||||
} do
|
||||
describe 'Repository License Detection', product_group: :source_code do
|
||||
after do
|
||||
project.remove_via_api!
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -12,11 +12,6 @@ RSpec.describe 'Project wikis', :js do
|
|||
let(:wiki) { create(:project_wiki, user: user, project: project) }
|
||||
let(:project) { create(:project, namespace: user.namespace, creator: user) }
|
||||
|
||||
context 'with legacy wiki rpcs' do
|
||||
before do
|
||||
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'User creates wiki page'
|
||||
it_behaves_like 'User deletes wiki page'
|
||||
it_behaves_like 'User previews wiki changes'
|
||||
|
@ -27,22 +22,4 @@ RSpec.describe 'Project wikis', :js do
|
|||
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
|
||||
|
|
|
@ -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
|
|
@ -75,78 +75,38 @@ RSpec.describe WikiHelper do
|
|||
|
||||
describe '#wiki_sort_controls' do
|
||||
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" }
|
||||
|
||||
def expected_link(sort, direction, icon_class)
|
||||
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
|
||||
path =
|
||||
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')
|
||||
def expected_link(direction, icon_class)
|
||||
path = "/#{wiki.project.full_path}/-/wikis/pages?direction=#{direction}"
|
||||
title = direction == 'desc' ? _('Sort direction: Ascending') : _('Sort direction: Descending')
|
||||
helper.link_to(path, type: 'button', class: classes, title: title) do
|
||||
helper.sprite_icon("sort-#{icon_class}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'wiki sorting enabled' do
|
||||
before do
|
||||
allow(wiki).to receive(:disable_sorting?).and_return(false)
|
||||
end
|
||||
|
||||
context 'initial call' do
|
||||
let(:sort) { nil }
|
||||
let(:direction) { nil }
|
||||
|
||||
it 'renders with default values' do
|
||||
expect(wiki_link).to eq(expected_link('title', 'asc', 'lowest'))
|
||||
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'sort by title' do
|
||||
let(:sort) { 'title' }
|
||||
context 'sort by asc order' do
|
||||
let(:direction) { 'asc' }
|
||||
|
||||
it 'renders a link with opposite direction' do
|
||||
expect(wiki_link).to eq(expected_link('title', 'aesc', 'lowest'))
|
||||
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'sort by created_at' do
|
||||
let(:sort) { 'created_at' }
|
||||
context 'sort by desc order' do
|
||||
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
|
||||
|
||||
context 'wiki sorting disabled' do
|
||||
before do
|
||||
allow(wiki).to receive(:disable_sorting?).and_return(true)
|
||||
end
|
||||
|
||||
context 'sort by created_at' do
|
||||
let(:sort) { 'created_at' }
|
||||
let(:direction) { 'asc' }
|
||||
|
||||
it 'ignores created_at and renders a link with opposite direction' do
|
||||
expect(wiki_link).to eq(expected_link(nil, 'asc', 'lowest'))
|
||||
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
|
||||
expect(wiki_link).to eq(expected_link('asc', 'highest'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
|
|||
allow_next_instance_of(Gitlab::ImportExport) do |instance|
|
||||
allow(instance).to receive(:storage_path).and_return(export_path)
|
||||
end
|
||||
project_wiki.wiki
|
||||
project_wiki.create_wiki_repository
|
||||
project_wiki.create_page("index", "test content")
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe Gitlab::Middleware::Go do
|
|||
|
||||
shared_examples 'go-get=1' do |enabled_protocol:|
|
||||
context 'with simple 2-segment project path' do
|
||||
let!(:project) { create(:project, :private, :repository) }
|
||||
let!(:project) { create(:project, :public, :repository) }
|
||||
|
||||
context 'with subpackages' do
|
||||
let(:path) { "#{project.full_path}/subpackage" }
|
||||
|
@ -51,6 +51,16 @@ RSpec.describe Gitlab::Middleware::Go do
|
|||
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
|
||||
let(:group) { create(:group, :nested) }
|
||||
let!(:project) { create(:project, :public, :repository, namespace: group) }
|
||||
|
@ -68,8 +78,10 @@ RSpec.describe Gitlab::Middleware::Go do
|
|||
end
|
||||
|
||||
shared_examples 'unauthorized' do
|
||||
it 'returns the 2-segment group path' do
|
||||
expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch)
|
||||
it 'returns unauthorized' do
|
||||
status_code, _headers, body = go
|
||||
expect(status_code).to eq(404)
|
||||
expect(body).to match_array([''])
|
||||
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)
|
||||
response = go
|
||||
|
||||
expect(response[0]).to eq(403)
|
||||
expect(response[0]).to eq(404)
|
||||
expect(response[1]['Content-Length']).to be_nil
|
||||
expect(response[2]).to eq([''])
|
||||
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)
|
||||
response = go
|
||||
|
||||
expect(response[0]).to eq(401)
|
||||
expect(response[0]).to eq(404)
|
||||
expect(response[1]['Content-Length']).to be_nil
|
||||
expect(response[2]).to eq([''])
|
||||
end
|
||||
|
|
|
@ -24,8 +24,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
|
||||
context 'migration to instrumentation classes data collection' do
|
||||
let_it_be(:instrumented_events) do
|
||||
instrumentation_classes = %w[AggregatedMetric RedisHLLMetric]
|
||||
::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
|
||||
end.compact.to_set
|
||||
|
|
|
@ -340,7 +340,7 @@ RSpec.describe Projects::UpdateService 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)
|
||||
|
||||
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}")
|
||||
|
||||
counter = double(:counter)
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
- Security::FindingsFinder
|
||||
- Security::PipelineVulnerabilitiesFinder
|
||||
- Security::ScanExecutionPoliciesFinder
|
||||
- Security::ScanResultPoliciesFinder
|
||||
- Security::TrainingProviders::BaseUrlFinder
|
||||
- Security::TrainingUrlsFinder
|
||||
- Security::TrainingProviders::KontraUrlFinder
|
||||
|
|
|
@ -48,7 +48,7 @@ RSpec.shared_examples 'wiki controller actions' do
|
|||
context 'when the wiki repository cannot be created' do
|
||||
before do
|
||||
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
|
||||
|
||||
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
|
||||
before do
|
||||
allow(Wiki).to receive(:for_container).and_return(wiki)
|
||||
wiki.wiki
|
||||
wiki.create_wiki_repository
|
||||
expect(wiki).to receive(:find_sidebar) do
|
||||
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# wiki
|
||||
# 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
|
||||
|
||||
let!(:wiki_page1) do
|
||||
|
@ -53,40 +53,4 @@ RSpec.shared_examples 'User views wiki pages' do |support_sorting_by_created_at
|
|||
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
|
||||
|
|
|
@ -15,7 +15,7 @@ RSpec.shared_examples 'model with wiki' do
|
|||
|
||||
context 'when the repository cannot be created' do
|
||||
before do
|
||||
expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
|
||||
expect(container.wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
|
||||
end
|
||||
|
||||
it 'returns false and adds a validation error' do
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'wiki model' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user, :commit_email) }
|
||||
|
||||
let(:wiki_container) { raise NotImplementedError }
|
||||
|
@ -124,36 +126,6 @@ RSpec.shared_examples 'wiki model' do
|
|||
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
|
||||
context 'when the wiki repository is empty' do
|
||||
it 'returns true' do
|
||||
|
@ -447,14 +419,6 @@ RSpec.shared_examples 'wiki model' do
|
|||
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
|
||||
it_behaves_like 'wiki model #find_page'
|
||||
end
|
||||
|
@ -473,14 +437,6 @@ RSpec.shared_examples 'wiki model' do
|
|||
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
|
||||
it_behaves_like 'wiki model #find_sidebar'
|
||||
end
|
||||
|
@ -490,7 +446,7 @@ RSpec.shared_examples 'wiki model' do
|
|||
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
|
||||
|
||||
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')
|
||||
end
|
||||
|
@ -694,14 +650,6 @@ RSpec.shared_examples 'wiki model' do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
describe '#update_page' do
|
||||
|
@ -800,17 +748,6 @@ RSpec.shared_examples 'wiki model' do
|
|||
include_context 'extended examples'
|
||||
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
|
||||
let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
|
||||
|
||||
|
@ -984,4 +921,40 @@ RSpec.shared_examples 'wiki model' do
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue