diff --git a/Gemfile b/Gemfile index 9a7dde64cae..71e537ab13b 100644 --- a/Gemfile +++ b/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' diff --git a/Gemfile.checksum b/Gemfile.checksum index dd7d32e1d62..a258da072f5 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -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"}, diff --git a/Gemfile.lock b/Gemfile.lock index bee897330e5..4f62d212ab8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index d9059e126f2..bb86695b9a3 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -190,7 +190,10 @@ export default {
-
+

{{ headerText }}

diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 83447744013..2b781c528ad 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -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 diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index bded1b3a566..017a1861905 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -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 diff --git a/app/models/concerns/has_wiki.rb b/app/models/concerns/has_wiki.rb index 89bcabafb84..53016ce62f4 100644 --- a/app/models/concerns/has_wiki.rb +++ b/app/models/concerns/has_wiki.rb @@ -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')) diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 02d13836559..b718c3a096f 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -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? - 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 + def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false) + create_wiki_repository unless repository_exists? + 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 + create_wiki_repository unless repository_exists? + find_page_with_repository_rpcs(title, version, load_content: load_content) 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 diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index ad07e0269f6..24b0b94eeb7 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -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 diff --git a/app/views/projects/work_items/index.html.haml b/app/views/projects/work_items/index.html.haml index 8575fd10ad3..69597aab7ef 100644 --- a/app/views/projects/work_items/index.html.haml +++ b/app/views/projects/work_items/index.html.haml @@ -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 diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index b59275c35df..d6900c397a0 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -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) diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml index 79c5bb62f28..e1252e91c10 100644 --- a/app/views/shared/wikis/pages.html.haml +++ b/app/views/shared/wikis/pages.html.haml @@ -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' diff --git a/config/feature_flags/development/wiki_find_page_with_normal_repository_rpcs.yml b/config/feature_flags/development/wiki_find_page_with_normal_repository_rpcs.yml deleted file mode 100644 index 31d98265984..00000000000 --- a/config/feature_flags/development/wiki_find_page_with_normal_repository_rpcs.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/development/wiki_list_pages_with_normal_repository_rpcs.yml b/config/feature_flags/development/wiki_list_pages_with_normal_repository_rpcs.yml deleted file mode 100644 index aaa5bb4abff..00000000000 --- a/config/feature_flags/development/wiki_list_pages_with_normal_repository_rpcs.yml +++ /dev/null @@ -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 diff --git a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml index fbf4be136cb..df64125b058 100644 --- a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml +++ b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml @@ -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 diff --git a/config/metrics/counts_28d/20210216181504_issues_edit_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210216181504_issues_edit_total_unique_counts_monthly.yml index fd67504fee9..bd654f16cd5 100644 --- a/config/metrics/counts_28d/20210216181504_issues_edit_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20210216181504_issues_edit_total_unique_counts_monthly.yml @@ -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 diff --git a/config/metrics/counts_28d/20210427105033_pipeline_authoring_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210427105033_pipeline_authoring_total_unique_counts_monthly.yml index a3f1a052531..698928834ed 100644 --- a/config/metrics/counts_28d/20210427105033_pipeline_authoring_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20210427105033_pipeline_authoring_total_unique_counts_monthly.yml @@ -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 diff --git a/config/metrics/counts_28d/20220315223227_error_tracking_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20220315223227_error_tracking_total_unique_counts_monthly.yml index 50a854eb455..646260aa65e 100644 --- a/config/metrics/counts_28d/20220315223227_error_tracking_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20220315223227_error_tracking_total_unique_counts_monthly.yml @@ -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 diff --git a/config/metrics/counts_28d/20220615103718_incident_management_timeline_event_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20220615103718_incident_management_timeline_event_total_unique_counts_monthly.yml index cfdd8987275..d379cf39983 100644 --- a/config/metrics/counts_28d/20220615103718_incident_management_timeline_event_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20220615103718_incident_management_timeline_event_total_unique_counts_monthly.yml @@ -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 diff --git a/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml b/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml index 9865183388f..424552f54e4 100644 --- a/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml +++ b/config/metrics/counts_7d/20210216174908_analytics_unique_visits_for_any_target.yml @@ -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 diff --git a/config/metrics/counts_7d/20210216181503_issues_edit_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210216181503_issues_edit_total_unique_counts_weekly.yml index 5863d5c6d33..8fce94a17e2 100644 --- a/config/metrics/counts_7d/20210216181503_issues_edit_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20210216181503_issues_edit_total_unique_counts_weekly.yml @@ -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 diff --git a/config/metrics/counts_7d/20210427105030_pipeline_authoring_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210427105030_pipeline_authoring_total_unique_counts_weekly.yml index 1439c31a1cb..3f688886a67 100644 --- a/config/metrics/counts_7d/20210427105030_pipeline_authoring_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20210427105030_pipeline_authoring_total_unique_counts_weekly.yml @@ -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 diff --git a/config/metrics/counts_7d/20220315223220_error_tracking_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20220315223220_error_tracking_total_unique_counts_weekly.yml index 851fd6d0925..e40a7d4cba6 100644 --- a/config/metrics/counts_7d/20220315223220_error_tracking_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20220315223220_error_tracking_total_unique_counts_weekly.yml @@ -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 diff --git a/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml index 964297bde96..9146e2c7c8b 100644 --- a/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml @@ -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 diff --git a/config/open_api.yml b/config/open_api.yml new file mode 100644 index 00000000000..8415a6bff3d --- /dev/null +++ b/config/open_api.yml @@ -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 diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml new file mode 100644 index 00000000000..59b21ecd048 --- /dev/null +++ b/doc/api/openapi/openapi_v2.yaml @@ -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 diff --git a/doc/development/database/strings_and_the_text_data_type.md b/doc/development/database/strings_and_the_text_data_type.md index 02adcfacf02..fb005e51902 100644 --- a/doc/development/database/strings_and_the_text_data_type.md +++ b/doc/development/database/strings_and_the_text_data_type.md @@ -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 diff --git a/doc/development/database/transaction_guidelines.md b/doc/development/database/transaction_guidelines.md index 098f6eb1027..26bb6c2ce8f 100644 --- a/doc/development/database/transaction_guidelines.md +++ b/doc/development/database/transaction_guidelines.md @@ -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. diff --git a/doc/integration/index.md b/doc/integration/index.md index 06a7620f477..147edcc9e0f 100644 --- a/doc/integration/index.md +++ b/doc/integration/index.md @@ -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 diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index fdfc63993fc..69f6e055240 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -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. diff --git a/lib/api/api.rb b/lib/api/api.rb index 28d9bddf81c..933c3f69075 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -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 diff --git a/lib/api/entities/metadata.rb b/lib/api/entities/metadata.rb new file mode 100644 index 00000000000..daa491ec42a --- /dev/null +++ b/lib/api/entities/metadata.rb @@ -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 diff --git a/lib/api/helpers/open_api.rb b/lib/api/helpers/open_api.rb new file mode 100644 index 00000000000..11602244b57 --- /dev/null +++ b/lib/api/helpers/open_api.rb @@ -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 diff --git a/lib/api/metadata.rb b/lib/api/metadata.rb index 747082430a9..3e42ffe336a 100644 --- a/lib/api/metadata.rb +++ b/lib/api/metadata.rb @@ -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 diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml index 3096af1b173..fbdaeecca5d 100644 --- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml @@ -33,3 +33,8 @@ test: stage: test script: - ./runmytests.sh + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml index 4fe37ceaeaa..3379ce2f649 100644 --- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml index 68b55b782cd..9584ec5deef 100644 --- a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml index acc4a9d2917..21dda92257e 100644 --- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml index b8d284532bd..5fcbb251672 100644 --- a/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb deleted file mode 100644 index cbe6ba7721a..00000000000 --- a/lib/gitlab/git/wiki.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/git/wiki_page.rb b/lib/gitlab/git/wiki_page.rb index 154c79f56b8..26d15daf093 100644 --- a/lib/gitlab/git/wiki_page.rb +++ b/lib/gitlab/git/wiki_page.rb @@ -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] diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb deleted file mode 100644 index ca839b232cf..00000000000 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index dcbb4557377..e7b4084d153 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -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 + # If a project is found and the user has access, we return the full project path + [project.full_path, project.default_branch] 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 diff --git a/lib/tasks/gitlab/openapi.rake b/lib/tasks/gitlab/openapi.rake new file mode 100644 index 00000000000..fd067a1bf0b --- /dev/null +++ b/lib/tasks/gitlab/openapi.rake @@ -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 diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb index 1005f168098..50df8afafaf 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb @@ -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 diff --git a/spec/factories/git_wiki_commit_details.rb b/spec/factories/git_wiki_commit_details.rb deleted file mode 100644 index fb3f2954b12..00000000000 --- a/spec/factories/git_wiki_commit_details.rb +++ /dev/null @@ -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 diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb index 6e130f96999..8ac17413df3 100644 --- a/spec/features/projects/wikis_spec.rb +++ b/spec/features/projects/wikis_spec.rb @@ -12,37 +12,14 @@ 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' - 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' - 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 + 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' + it_behaves_like 'User views wiki sidebar' + it_behaves_like 'User views Git access wiki page' end diff --git a/spec/features/work_items/work_item_spec.rb b/spec/features/work_items/work_item_spec.rb new file mode 100644 index 00000000000..686b82de868 --- /dev/null +++ b/spec/features/work_items/work_item_spec.rb @@ -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 diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb index 06bc89b4950..59624dc0682 100644 --- a/spec/helpers/wiki_helper_spec.rb +++ b/spec/helpers/wiki_helper_spec.rb @@ -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(:direction) { nil } - 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')) - 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 + it 'renders with default values' do + expect(wiki_link).to eq(expected_link('desc', 'lowest')) end end - context 'wiki sorting disabled' do - before do - allow(wiki).to receive(:disable_sorting?).and_return(true) + context 'sort by asc order' do + let(:direction) { 'asc' } + + it 'renders a link with opposite direction' do + expect(wiki_link).to eq(expected_link('desc', 'lowest')) end + end - context 'sort by created_at' do - let(:sort) { 'created_at' } - let(:direction) { 'asc' } + context 'sort by desc order' do + let(:direction) { 'desc' } - 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 + it 'renders a link with opposite direction' do + expect(wiki_link).to eq(expected_link('asc', 'highest')) end end end diff --git a/spec/lib/api/helpers/open_api_spec.rb b/spec/lib/api/helpers/open_api_spec.rb new file mode 100644 index 00000000000..fb14f7fe001 --- /dev/null +++ b/spec/lib/api/helpers/open_api_spec.rb @@ -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 diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb deleted file mode 100644 index 05c7ac149e4..00000000000 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ /dev/null @@ -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+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 diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb deleted file mode 100644 index 8a169acb69c..00000000000 --- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb +++ /dev/null @@ -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 diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index c936d2bc27d..0e6173b611f 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -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 diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index bc1d53b2ccb..e795577d203 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -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 diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index cbce867cb98..d0b935d59dd 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -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 diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 9f02d307a93..7d8951bf111 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -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) diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index 1ac8e49fb45..c8af07905c2 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -55,6 +55,7 @@ - Security::FindingsFinder - Security::PipelineVulnerabilitiesFinder - Security::ScanExecutionPoliciesFinder +- Security::ScanResultPoliciesFinder - Security::TrainingProviders::BaseUrlFinder - Security::TrainingUrlsFinder - Security::TrainingProviders::KontraUrlFinder diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index 9bb09dce289..5d77ed5fdfc 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb index 979c8ac2e84..9b5326026b1 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb index 0357b7462fb..65bc6c10490 100644 --- a/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index f5ea3dd18bd..b1aa90449e1 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -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