summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-08 12:20:17 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-08 12:20:17 +0000
commit6728ed6fe203d0613ee63c89a08a70fffb93405c (patch)
tree9eddfee7a854efd47d85899c1524fd4bd10ce8e4
parent60028378dd5e5e7844810e4a2aa2934a58f738ca (diff)
downloadgitlab-ce-6728ed6fe203d0613ee63c89a08a70fffb93405c.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.eslintignore1
-rw-r--r--.rubocop_todo/database/multiple_databases.yml1
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/events/repositories/keep_around_refs_created_event.rb14
-rw-r--r--app/models/hooks/web_hook.rb15
-rw-r--r--app/policies/project_policy.rb11
-rw-r--r--app/services/issues/export_csv_service.rb4
-rw-r--r--app/services/merge_requests/export_csv_service.rb4
-rw-r--r--app/services/web_hooks/log_execution_service.rb30
-rw-r--r--app/views/dashboard/_activities.html.haml2
-rw-r--r--app/views/dashboard/groups/_groups.html.haml2
-rw-r--r--app/views/groups/_activities.html.haml2
-rw-r--r--app/views/groups/_archived_projects.html.haml3
-rw-r--r--app/views/groups/runners/_settings.html.haml14
-rw-r--r--app/views/ide/_show.html.haml2
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml3
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml2
-rw-r--r--app/views/shared/nav/_sidebar_submenu.html.haml2
-rw-r--r--config/feature_flags/development/issues_full_text_search.yml2
-rw-r--r--config/feature_flags/development/source_editor_toolbar.yml8
-rw-r--r--db/migrate/20220303190555_add_comment_to_deployment_approvals.rb10
-rw-r--r--db/migrate/20220303191047_add_text_limit_to_deployment_approvals_comment.rb13
-rw-r--r--db/schema_migrations/202203031905551
-rw-r--r--db/schema_migrations/202203031910471
-rw-r--r--db/structure.sql4
-rw-r--r--doc/api/deployments.md6
-rw-r--r--doc/ci/environments/deployment_approvals.md2
-rw-r--r--doc/development/snowplow/implementation.md5
-rw-r--r--doc/integration/security_partners/index.md4
-rw-r--r--doc/integration/twitter.md4
-rw-r--r--doc/ssh/index.md2
-rw-r--r--doc/topics/autodevops/customize.md2
-rw-r--r--doc/topics/autodevops/stages.md2
-rw-r--r--doc/topics/gitlab_flow.md2
-rw-r--r--doc/user/analytics/code_review_analytics.md2
-rw-r--r--doc/user/clusters/applications.md4
-rw-r--r--doc/user/clusters/crossplane.md2
-rw-r--r--doc/user/clusters/integrations.md6
-rw-r--r--doc/user/clusters/migrating_from_gma_to_project_template.md2
-rw-r--r--doc/user/project/issues/csv_export.md10
-rw-r--r--doc/user/project/merge_requests/csv_export.md8
-rw-r--r--doc/user/project/web_ide/index.md14
-rw-r--r--lib/gitlab/database.rb10
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/import_export/group/relation_tree_restorer.rb2
-rw-r--r--locale/gitlab.pot15
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock35
-rwxr-xr-xqa/bin/contract32
-rw-r--r--qa/contracts/.gitignore2
-rw-r--r--qa/contracts/consumer/.node-version1
-rw-r--r--qa/contracts/consumer/endpoints/merge_request.js42
-rw-r--r--qa/contracts/consumer/fixtures/diffs.fixture.js89
-rw-r--r--qa/contracts/consumer/fixtures/discussions.fixture.js85
-rw-r--r--qa/contracts/consumer/fixtures/metadata.fixture.js96
-rw-r--r--qa/contracts/consumer/package.json17
-rw-r--r--qa/contracts/consumer/specs/diffs.spec.js35
-rw-r--r--qa/contracts/consumer/specs/discussions.spec.js35
-rw-r--r--qa/contracts/consumer/specs/metadata.spec.js35
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json228
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json235
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json222
-rw-r--r--qa/contracts/provider/Rakefile17
-rw-r--r--qa/contracts/provider/environments/base.rb26
-rw-r--r--qa/contracts/provider/environments/local.rb12
-rw-r--r--qa/contracts/provider/spec/diffs_helper.rb15
-rw-r--r--qa/contracts/provider/spec/discussions_helper.rb15
-rw-r--r--qa/contracts/provider/spec/metadata_helper.rb15
-rw-r--r--spec/features/groups/settings/ci_cd_spec.rb18
-rw-r--r--spec/lib/gitlab/database_spec.rb40
-rw-r--r--spec/models/hooks/web_hook_spec.rb12
-rw-r--r--spec/policies/project_policy_spec.rb96
-rw-r--r--spec/services/web_hooks/log_execution_service_spec.rb69
-rw-r--r--spec/views/projects/runners/_specific_runners.html.haml_spec.rb4
76 files changed, 1725 insertions, 67 deletions
diff --git a/.eslintignore b/.eslintignore
index 1d069e19385..b7769ef8970 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -9,3 +9,4 @@
/sitespeed-result/
/fixtures/**/*.graphql
spec/fixtures/**/*.graphql
+**/contracts/consumer/
diff --git a/.rubocop_todo/database/multiple_databases.yml b/.rubocop_todo/database/multiple_databases.yml
index f601106149a..8364ee2af7f 100644
--- a/.rubocop_todo/database/multiple_databases.yml
+++ b/.rubocop_todo/database/multiple_databases.yml
@@ -16,7 +16,6 @@ Database/MultipleDatabases:
- lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
- lib/gitlab/database.rb
- lib/gitlab/health_checks/db_check.rb
- - lib/gitlab/import_export/group/relation_tree_restorer.rb
- lib/gitlab/seeder.rb
- spec/db/schema_spec.rb
- spec/initializers/database_config_spec.rb
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 3cf1f69b08c..495c87a695c 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -431,7 +431,7 @@ export default {
class="js-ide-edit-blob"
data-qa-selector="edit_in_ide_button"
>
- {{ __('Edit in Web IDE') }}
+ {{ __('Open in Web IDE') }}
</gl-dropdown-item>
</template>
diff --git a/app/events/repositories/keep_around_refs_created_event.rb b/app/events/repositories/keep_around_refs_created_event.rb
new file mode 100644
index 00000000000..2ac499e6e21
--- /dev/null
+++ b/app/events/repositories/keep_around_refs_created_event.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Repositories
+ class KeepAroundRefsCreatedEvent < ::Gitlab::EventStore::Event
+ def schema
+ {
+ 'type' => 'object',
+ 'properties' => {
+ 'project_id' => { 'type' => 'integer' }
+ }
+ }
+ end
+ end
+end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7e538238cbd..88941df691c 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -37,14 +37,14 @@ class WebHook < ApplicationRecord
!temporarily_disabled? && !permanently_disabled?
end
- def temporarily_disabled?
- return false unless web_hooks_disable_failed?
+ def temporarily_disabled?(ignore_flag: false)
+ return false unless ignore_flag || web_hooks_disable_failed?
disabled_until.present? && disabled_until >= Time.current
end
- def permanently_disabled?
- return false unless web_hooks_disable_failed?
+ def permanently_disabled?(ignore_flag: false)
+ return false unless ignore_flag || web_hooks_disable_failed?
recent_failures > FAILURE_THRESHOLD
end
@@ -106,6 +106,13 @@ class WebHook < ApplicationRecord
save(validate: false)
end
+ def active_state(ignore_flag: false)
+ return :permanently_disabled if permanently_disabled?(ignore_flag: ignore_flag)
+ return :temporarily_disabled if temporarily_disabled?(ignore_flag: ignore_flag)
+
+ :enabled
+ end
+
# @return [Boolean] Whether or not the WebHook is currently throttled.
def rate_limited?
return false unless rate_limit
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 147ca9c9881..069096e5bc0 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -194,6 +194,10 @@ class ProjectPolicy < BasePolicy
condition(:"#{f}_disabled", score: 32) { !access_allowed_to?(f.to_sym) }
end
+ condition(:project_runner_registration_allowed) do
+ Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?('project')
+ end
+
# `:read_project` may be prevented in EE, but `:read_project_for_iids` should
# not.
rule { guest | admin }.enable :read_project_for_iids
@@ -230,6 +234,8 @@ class ProjectPolicy < BasePolicy
enable :set_emails_disabled
enable :set_show_default_award_emojis
enable :set_warn_about_potentially_unwanted_characters
+
+ enable :register_project_runners
end
rule { can?(:guest_access) }.policy do
@@ -453,6 +459,7 @@ class ProjectPolicy < BasePolicy
enable :update_freeze_period
enable :destroy_freeze_period
enable :admin_feature_flags_client
+ enable :register_project_runners
enable :update_runners_registration_token
enable :admin_project_google_cloud
end
@@ -727,6 +734,10 @@ class ProjectPolicy < BasePolicy
enable :access_security_and_compliance
end
+ rule { ~admin & ~project_runner_registration_allowed }.policy do
+ prevent :register_project_runners
+ end
+
private
def user_is_user?
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
index a30644e2105..7076e858155 100644
--- a/app/services/issues/export_csv_service.rb
+++ b/app/services/issues/export_csv_service.rb
@@ -23,11 +23,11 @@ module Issues
def header_to_value_hash
{
+ 'Title' => 'title',
+ 'Description' => 'description',
'Issue ID' => 'iid',
'URL' => -> (issue) { issue_url(issue) },
- 'Title' => 'title',
'State' => -> (issue) { issue.closed? ? 'Closed' : 'Open' },
- 'Description' => 'description',
'Author' => 'author_name',
'Author Username' => -> (issue) { issue.author&.username },
'Assignee' => -> (issue) { issue.assignees.map(&:name).join(', ') },
diff --git a/app/services/merge_requests/export_csv_service.rb b/app/services/merge_requests/export_csv_service.rb
index 8f2a70575e5..1f8dec69ef0 100644
--- a/app/services/merge_requests/export_csv_service.rb
+++ b/app/services/merge_requests/export_csv_service.rb
@@ -13,11 +13,11 @@ module MergeRequests
def header_to_value_hash
{
+ 'Title' => 'title',
+ 'Description' => 'description',
'MR IID' => 'iid',
'URL' => -> (merge_request) { merge_request_url(merge_request) },
- 'Title' => 'title',
'State' => 'state',
- 'Description' => 'description',
'Source Branch' => 'source_branch',
'Target Branch' => 'target_branch',
'Source Project ID' => 'source_project_id',
diff --git a/app/services/web_hooks/log_execution_service.rb b/app/services/web_hooks/log_execution_service.rb
index 04b9ff59382..0ee7c41469f 100644
--- a/app/services/web_hooks/log_execution_service.rb
+++ b/app/services/web_hooks/log_execution_service.rb
@@ -12,8 +12,9 @@ module WebHooks
def initialize(hook:, log_data:, response_category:)
@hook = hook
- @log_data = log_data
+ @log_data = log_data.transform_keys(&:to_sym)
@response_category = response_category
+ @prev_state = hook.active_state(ignore_flag: true)
end
def execute
@@ -24,7 +25,7 @@ module WebHooks
private
def log_execution
- WebHookLog.create!(web_hook: hook, **log_data.transform_keys(&:to_sym))
+ WebHookLog.create!(web_hook: hook, **log_data)
end
# Perform this operation within an `Gitlab::ExclusiveLease` lock to make it
@@ -41,11 +42,36 @@ module WebHooks
when :failed
hook.failed!
end
+
+ log_state_change
end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
raise if raise_lock_error?
end
+ def log_state_change
+ new_state = hook.active_state(ignore_flag: true)
+
+ return if @prev_state == new_state
+
+ Gitlab::AuthLogger.info(
+ message: 'WebHook change active_state',
+ # identification
+ hook_id: hook.id,
+ hook_type: hook.type,
+ project_id: hook.project_id,
+ group_id: hook.group_id,
+ # relevant data
+ prev_state: @prev_state,
+ new_state: new_state,
+ duration: log_data[:execution_duration],
+ response_status: log_data[:response_status],
+ recent_hook_failures: hook.recent_failures,
+ # context
+ **Gitlab::ApplicationContext.current
+ )
+ end
+
def lock_name
"web_hooks:update_hook_failure_state:#{hook.id}"
end
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index ec07c636b79..7c948260d4b 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -6,4 +6,4 @@
.content_list
.loading
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md')
diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml
index d5cd4b66e2b..601b6a8b1a7 100644
--- a/app/views/dashboard/groups/_groups.html.haml
+++ b/app/views/dashboard/groups/_groups.html.haml
@@ -1,4 +1,2 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center.prepend-top-20
- .gl-spinner.gl-spinner-md
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
index 1695d3b5539..614d9610f31 100644
--- a/app/views/groups/_activities.html.haml
+++ b/app/views/groups/_activities.html.haml
@@ -6,4 +6,4 @@
.content_list
.loading
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md')
diff --git a/app/views/groups/_archived_projects.html.haml b/app/views/groups/_archived_projects.html.haml
index 959c26acae0..21107cc22a1 100644
--- a/app/views/groups/_archived_projects.html.haml
+++ b/app/views/groups/_archived_projects.html.haml
@@ -4,5 +4,4 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center.prepend-top-20
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md', css_class: 'gl-mt-6')
diff --git a/app/views/groups/runners/_settings.html.haml b/app/views/groups/runners/_settings.html.haml
index 55960703f9a..bbcadc08a8b 100644
--- a/app/views/groups/runners/_settings.html.haml
+++ b/app/views/groups/runners/_settings.html.haml
@@ -1,3 +1,17 @@
+- if Feature.enabled?(:runner_list_group_view_vue_ui, @group, default_enabled: :yaml)
+ .gl-card.gl-px-8.gl-py-6.gl-line-height-20
+ .gl-card-body.gl-display-flex{ :class => "gl-p-0!" }
+ .gl-banner-illustration
+ = image_tag('illustrations/rocket-launch-md.svg', alt: s_('Runners|Rocket launch illustration'))
+ .gl-banner-content
+ %h1.gl-banner-title
+ = s_('Runners|New group runners view')
+ %p
+ = s_('Runners|The new view gives you more space and better visibility into your fleet of runners.')
+ %a.btn.btn-confirm.btn-md.gl-button{ :href => group_runners_path(@group) }
+ %span.gl-button-text
+ = s_('Runners|Take me there!')
+
= render 'shared/runners/runner_description'
%hr
diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml
index 755c4151115..95c15612adf 100644
--- a/app/views/ide/_show.html.haml
+++ b/app/views/ide/_show.html.haml
@@ -9,5 +9,5 @@
#ide.ide-loading{ data: ide_data }
.text-center
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md')
%h2.clgray= _('Loading the GitLab IDE...')
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index c5a0b6a1428..05166395067 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -11,4 +11,4 @@
.content_list.project-activity{ :"data-href" => activity_project_path(@project) }
.loading
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md')
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index a7667d03138..7fe4fb5ee9a 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -83,7 +83,7 @@
.mr-loading-status
.loading.hide
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'lg')
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 4cabb930433..b6700c9ed1e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -16,5 +16,4 @@
- if @commit
.network-graph.gl-bg-white.gl-overflow-scroll.gl-overflow-x-hidden{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
- .text-center.gl-mt-3
- .gl-spinner.gl-spinner-md
+ = gl_loading_icon(size: 'md', css_class: 'gl-mt-3')
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 1357846876e..3634bacb6ec 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -2,7 +2,7 @@
= _('Specific runners')
.bs-callout.help-callout
- - if valid_runner_registrars.include?('project')
+ - if can?(current_user, :register_project_runners, @project)
= _('These runners are specific to this project.')
- if params[:ci_runner_templates]
%hr
diff --git a/app/views/shared/nav/_sidebar_submenu.html.haml b/app/views/shared/nav/_sidebar_submenu.html.haml
index 750e6c9ee57..344dafe7c0f 100644
--- a/app/views/shared/nav/_sidebar_submenu.html.haml
+++ b/app/views/shared/nav/_sidebar_submenu.html.haml
@@ -4,7 +4,7 @@
%strong.fly-out-top-item-name
= sidebar_menu.title
- if sidebar_menu.has_pill?
- %span.badge.badge-pill.count.fly-out-badge{ **sidebar_menu.pill_html_options }
+ = gl_badge_tag({ variant: :info, size: :sm }, { class: "count fly-out-badge #{sidebar_menu.pill_html_options[:class]}" }) do
= number_with_delimiter(sidebar_menu.pill_count)
- if sidebar_menu.has_renderable_items?
diff --git a/config/feature_flags/development/issues_full_text_search.yml b/config/feature_flags/development/issues_full_text_search.yml
index 35af801e3ab..354dbede75f 100644
--- a/config/feature_flags/development/issues_full_text_search.yml
+++ b/config/feature_flags/development/issues_full_text_search.yml
@@ -1,7 +1,7 @@
---
name: issues_full_text_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71913
-rollout_issue_url:
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354784
milestone: '14.5'
type: development
group: group::project management
diff --git a/config/feature_flags/development/source_editor_toolbar.yml b/config/feature_flags/development/source_editor_toolbar.yml
new file mode 100644
index 00000000000..6fe2dd2d306
--- /dev/null
+++ b/config/feature_flags/development/source_editor_toolbar.yml
@@ -0,0 +1,8 @@
+---
+name: source_editor_toolbar
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82304
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354748
+milestone: '14.9'
+type: development
+group: group::editor
+default_enabled: false
diff --git a/db/migrate/20220303190555_add_comment_to_deployment_approvals.rb b/db/migrate/20220303190555_add_comment_to_deployment_approvals.rb
new file mode 100644
index 00000000000..56b873c009a
--- /dev/null
+++ b/db/migrate/20220303190555_add_comment_to_deployment_approvals.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddCommentToDeploymentApprovals < Gitlab::Database::Migration[1.0]
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20220303191047_add_text_limit_to_deployment_approvals_comment
+ def change
+ add_column :deployment_approvals, :comment, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20220303191047_add_text_limit_to_deployment_approvals_comment.rb b/db/migrate/20220303191047_add_text_limit_to_deployment_approvals_comment.rb
new file mode 100644
index 00000000000..70c7f5f3a7b
--- /dev/null
+++ b/db/migrate/20220303191047_add_text_limit_to_deployment_approvals_comment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddTextLimitToDeploymentApprovalsComment < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :deployment_approvals, :comment, 255
+ end
+
+ def down
+ remove_text_limit :deployment_approvals, :comment
+ end
+end
diff --git a/db/schema_migrations/20220303190555 b/db/schema_migrations/20220303190555
new file mode 100644
index 00000000000..08db64ca2a4
--- /dev/null
+++ b/db/schema_migrations/20220303190555
@@ -0,0 +1 @@
+f8fa8b83da24bf98c97447a2940c8ca801532c80395b6a65c11f83515f811652 \ No newline at end of file
diff --git a/db/schema_migrations/20220303191047 b/db/schema_migrations/20220303191047
new file mode 100644
index 00000000000..6e933c08f6b
--- /dev/null
+++ b/db/schema_migrations/20220303191047
@@ -0,0 +1 @@
+19566152e16a92263dd5dcfd66299e3b9d8b82acd4edb4bba21f6b9b06fc8070 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 69ae160571c..bbeecb69858 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14264,7 +14264,9 @@ CREATE TABLE deployment_approvals (
user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
- status smallint NOT NULL
+ status smallint NOT NULL,
+ comment text,
+ CONSTRAINT check_e2eb6a17d8 CHECK ((char_length(comment) <= 255))
);
CREATE SEQUENCE deployment_approvals_id_seq
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 87ff6549540..4a09f9a6605 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -465,9 +465,10 @@ POST /projects/:id/deployments/:deployment_id/approval
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `deployment_id` | integer | yes | The ID of the deployment. |
| `status` | string | yes | The status of the approval (either `approved` or `rejected`). |
+| `comment` | string | no | A comment to go with the approval |
```shell
-curl --data "status=approved" \
+curl --data "status=approved&comment=Looks good to me" \
--header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments/1/approval"
```
@@ -484,6 +485,7 @@ Example response:
"web_url": "http://localhost:3000/root"
},
"status": "approved",
- "created_at": "2022-02-24T20:22:30.097Z"
+ "created_at": "2022-02-24T20:22:30.097Z",
+ "comment":"Looks good to me"
}
```
diff --git a/doc/ci/environments/deployment_approvals.md b/doc/ci/environments/deployment_approvals.md
index d8e6bb629c3..acae9873d28 100644
--- a/doc/ci/environments/deployment_approvals.md
+++ b/doc/ci/environments/deployment_approvals.md
@@ -89,7 +89,7 @@ Using the [Deployments API](../../api/deployments.md#approve-or-reject-a-blocked
Example:
```shell
-curl --data "status=approved" \
+curl --data "status=approved&comment=Looks good to me" \
--header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments/1/approval"
```
diff --git a/doc/development/snowplow/implementation.md b/doc/development/snowplow/implementation.md
index 228fdff4413..7b815fff673 100644
--- a/doc/development/snowplow/implementation.md
+++ b/doc/development/snowplow/implementation.md
@@ -47,10 +47,7 @@ To implement tracking for HAML or Vue templates, add a [`data-track` attribute](
The following example shows `data-track-*` attributes assigned to a button:
```haml
-%button.btn{ data: { track: { action: "click_button", label: "template_preview", property: "my-template" } } }
-
-// or
-// %button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
+%button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
```
```html
diff --git a/doc/integration/security_partners/index.md b/doc/integration/security_partners/index.md
index 51c2c72769e..50a7b3b717b 100644
--- a/doc/integration/security_partners/index.md
+++ b/doc/integration/security_partners/index.md
@@ -12,16 +12,16 @@ each security partner:
<!-- vale gitlab.Spelling = NO -->
-- [Accurics](https://readme.accurics.com/1409/)
- [Anchore](https://docs.anchore.com/current/docs/using/integration/ci_cd/gitlab/)
- [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed)
- [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration)
- [Deepfactor](https://docs.deepfactor.io/hc/en-us/articles/1500008981941)
- [GrammaTech](https://www.grammatech.com/codesonar-gitlab-integration)
-- [Indeni](https://cloudrail.app/doc/integrate-with-ci-cd/gitlab-instructions/)
+- [Indeni](https://docs.cloudrail.app/#/integrations/gitlab)
- [JScrambler](https://docs.jscrambler.com/code-integrity/documentation/gitlab-ci-integration)
- [Semgrep](https://semgrep.dev/for/gitlab)
- [StackHawk](https://docs.stackhawk.com/continuous-integration/gitlab.html)
+- [Tenable](https://docs.tenable.com/tenableio/Content/ContainerSecurity/GetStarted.htm)
- [Venafi](https://marketplace.venafi.com/details/gitlab-ci-cd/)
- [Veracode](https://community.veracode.com/s/knowledgeitem/gitlab-ci-MCEKSYPRWL35BRTGOVI55SK5RI4A)
- [WhiteSource](https://www.whitesourcesoftware.com/gitlab/)
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index e1f67df76c3..3aba6b70b94 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -9,9 +9,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
To enable the Twitter OmniAuth provider you must register your application with
Twitter. Twitter generates a client ID and secret key for you to use.
-1. Sign in to [Twitter Application Management](https://developer.twitter.com/apps).
+1. Sign in to [Twitter Application Management](https://apps.twitter.com).
-1. Select "Create new app"
+1. Select "Create new app".
1. Fill in the application details.
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or
diff --git a/doc/ssh/index.md b/doc/ssh/index.md
index 35ca9a23179..1de213a796b 100644
--- a/doc/ssh/index.md
+++ b/doc/ssh/index.md
@@ -408,7 +408,7 @@ If you are using [EGit](https://www.eclipse.org/egit/), you can [add your SSH ke
## Use SSH on Microsoft Windows
If you're running Windows 10, you can either use the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install)
-with [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/install-win10#update-to-wsl-2) which
+with [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/install#update-to-wsl-2) which
has both `git` and `ssh` preinstalled, or install [Git for Windows](https://gitforwindows.org) to
use SSH through Powershell.
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index 1cbf7f3bb69..503774ef6b5 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -26,7 +26,7 @@ used for the build.
Specify either:
-- The CI/CD variable `BUILDPACK_URL` according to [`pack`'s specifications](https://buildpacks.io/docs/app-developer-guide/specific-buildpacks/).
+- The CI/CD variable `BUILDPACK_URL` according to [`pack`'s specifications](https://buildpacks.io/docs/app-developer-guide/specify-buildpacks/).
- A [`project.toml` project descriptor](https://buildpacks.io/docs/app-developer-guide/using-project-descriptor/) with the buildpacks you would like to include.
### Custom buildpacks with Herokuish
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 1f3fa3394ea..790b46b6310 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -112,7 +112,7 @@ Herokuish, with the following caveats:
converted to a Cloud Native Buildpack using Heroku's
[`cnb-shim`](https://github.com/heroku/cnb-shim).
- `BUILDPACK_URL` must be in a format
- [supported by `pack`](https://buildpacks.io/docs/app-developer-guide/specific-buildpacks/).
+ [supported by `pack`](https://buildpacks.io/docs/app-developer-guide/specify-buildpacks/).
- The `/bin/herokuish` command is not present in the built image, and prefixing
commands with `/bin/herokuish procfile exec` is no longer required (nor possible).
Instead, custom commands should be prefixed with `/cnb/lifecycle/launcher`
diff --git a/doc/topics/gitlab_flow.md b/doc/topics/gitlab_flow.md
index 3bca33b32b7..d35eba0d782 100644
--- a/doc/topics/gitlab_flow.md
+++ b/doc/topics/gitlab_flow.md
@@ -190,7 +190,7 @@ By doing this, you minimize the length of time during which you have to apply bu
After announcing a release branch, only add serious bug fixes to the branch.
If possible, first merge these bug fixes into `main`, and then cherry-pick them into the release branch.
If you start by merging into the release branch, you might forget to cherry-pick them into `main`, and then you'd encounter the same bug in subsequent releases.
-Merging into `main` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo).
+Merging into `main` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first/) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo).
Every time you include a bug fix in a release branch, increase the patch version (to comply with [Semantic Versioning](https://semver.org/)) by setting a new tag.
Some projects also have a stable branch that points to the same commit as the latest released branch.
In this flow, it is not common to have a production branch (or Git flow `main` branch).
diff --git a/doc/user/analytics/code_review_analytics.md b/doc/user/analytics/code_review_analytics.md
index 18a6ca20bc7..dc02512702a 100644
--- a/doc/user/analytics/code_review_analytics.md
+++ b/doc/user/analytics/code_review_analytics.md
@@ -40,7 +40,7 @@ To view Code Review Analytics:
## Use cases
-This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/#delaney-development-team-lead)
+This feature is designed for [development team leaders](https://about.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
and others who want to understand broad code review dynamics, and identify patterns to explain them.
You can use Code Review Analytics to:
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index f880e603133..2bcfea50ee3 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -980,7 +980,7 @@ podAnnotations:
The only information to be changed here is the profile name which is `profile-one`
in this example. Refer to the
-[AppArmor tutorial](https://kubernetes.io/docs/tutorials/clusters/apparmor/#securing-a-pod)
+[AppArmor tutorial](https://kubernetes.io/docs/tutorials/security/apparmor/#securing-a-pod)
for more information on how AppArmor is integrated in Kubernetes.
#### Using PodSecurityPolicy in your deployments
@@ -1017,7 +1017,7 @@ securityPolicies:
```
This example creates a single policy named `example` with the provided specification,
-and enables [AppArmor annotations](https://kubernetes.io/docs/tutorials/clusters/apparmor/#podsecuritypolicy-annotations) on it.
+and enables [AppArmor annotations](https://kubernetes.io/docs/tutorials/security/apparmor/#podsecuritypolicy-annotations) on it.
Support for installing the AppArmor managed application is provided by the
GitLab Container Security group. If you run into unknown issues,
diff --git a/doc/user/clusters/crossplane.md b/doc/user/clusters/crossplane.md
index e6540e68f71..9e4c672ac45 100644
--- a/doc/user/clusters/crossplane.md
+++ b/doc/user/clusters/crossplane.md
@@ -80,7 +80,7 @@ provided can manage resources in the `database.crossplane.io` API group:
## Configure Crossplane with a cloud provider
-See [Configure Your Cloud Provider Account](https://crossplane.github.io/docs/v0.4/cloud-providers.html)
+See [Configure Your Cloud Provider Account](https://crossplane.github.io/docs/v1.6/)
to configure the installed cloud provider stack with a user account.
The Secret, and the Provider resource referencing the Secret, must be
diff --git a/doc/user/clusters/integrations.md b/doc/user/clusters/integrations.md
index 43a35177d28..74f6ec283ea 100644
--- a/doc/user/clusters/integrations.md
+++ b/doc/user/clusters/integrations.md
@@ -113,9 +113,9 @@ To use this integration:
`gitlab-managed-apps` namespace.
1. The `Service` resource must be called `elastic-stack-elasticsearch-master`
and expose the Elasticsearch API on port `9200`.
-1. The logs are expected to be [Filebeat container logs](https://www.elastic.co/guide/en/beats/filebeat/7.x/filebeat-input-container.html)
- following the [7.x log structure](https://www.elastic.co/guide/en/beats/filebeat/7.x/exported-fields-log.html)
- and include [Kubernetes metadata](https://www.elastic.co/guide/en/beats/filebeat/7.x/add-kubernetes-metadata.html).
+1. The logs are expected to be [Filebeat container logs](https://www.elastic.co/guide/en/beats/filebeat/7.16/filebeat-input-container.html)
+ following the [7.x log structure](https://www.elastic.co/guide/en/beats/filebeat/7.16/exported-fields-log.html)
+ and include [Kubernetes metadata](https://www.elastic.co/guide/en/beats/filebeat/7.16/add-kubernetes-metadata.html).
You can manage your Elastic Stack however you like, but as an example, you can
use [this Elastic Stack chart](https://gitlab.com/gitlab-org/charts/elastic-stack) to get up and
diff --git a/doc/user/clusters/migrating_from_gma_to_project_template.md b/doc/user/clusters/migrating_from_gma_to_project_template.md
index ef804331026..09453262fbb 100644
--- a/doc/user/clusters/migrating_from_gma_to_project_template.md
+++ b/doc/user/clusters/migrating_from_gma_to_project_template.md
@@ -120,7 +120,7 @@ you want to manage with the Cluster Management Project.
## Backup and uninstall cert-manager v0.10
-1. Follow the [official docs](https://docs.cert-manager.io/en/release-0.10/tasks/backup-restore-crds.html) on how to
+1. Follow the [official docs](https://cert-manager.io/docs/tutorials/backup/) on how to
backup your cert-manager v0.10 data.
1. Uninstall cert-manager by editing the setting all the occurrences of `installed: true` to `installed: false` in the
`applications/cert-manager/helmfile.yaml` file.
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
index 947fbdcc2d1..e5d698fa97a 100644
--- a/doc/user/project/issues/csv_export.md
+++ b/doc/user/project/issues/csv_export.md
@@ -52,7 +52,7 @@ export, after which the email is prepared.
## Sorting
-Exported issues are always sorted by `Issue ID`.
+Exported issues are always sorted by `Title`.
## Format
@@ -63,11 +63,11 @@ the values:
| Column | Description |
|------------------------------------------|-----------------------------------------------------------|
+| Title | Issue `title` |
+| Description | Issue `description` |
| Issue ID | Issue `iid` |
| URL | A link to the issue on GitLab |
-| Title | Issue `title` |
| State | `Open` or `Closed` |
-| Description | Issue `description` |
| Author | Full name of the issue author |
| Author Username | Username of the author, with the `@` symbol omitted |
| Assignee | Full name of the issue assignee |
@@ -85,6 +85,10 @@ the values:
| [Epic](../../group/epics/index.md) ID | ID of the parent epic, introduced in 12.7 |
| [Epic](../../group/epics/index.md) Title | Title of the parent epic, introduced in 12.7 |
+In GitLab 14.7 and earlier, the first two columns were `Issue ID` and `URL`,
+which [caused an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/34769)
+when importing back into GitLab.
+
## Limitations
- Export Issues to CSV is not available at the Group's Issues List.
diff --git a/doc/user/project/merge_requests/csv_export.md b/doc/user/project/merge_requests/csv_export.md
index 6dbbab316a0..df527ec6ebf 100644
--- a/doc/user/project/merge_requests/csv_export.md
+++ b/doc/user/project/merge_requests/csv_export.md
@@ -18,11 +18,11 @@ The following table shows what attributes will be present in the CSV.
| Column | Description |
|--------------------|--------------------------------------------------------------|
+| Title | Merge request title |
+| Description | Merge request description |
| MR ID | MR `iid` |
| URL | A link to the merge request on GitLab |
-| Title | Merge request title |
| State | Opened, Closed, Locked, or Merged |
-| Description | Merge request description |
| Source Branch | Source branch |
| Target Branch | Target branch |
| Source Project ID | ID of the source project |
@@ -39,6 +39,10 @@ The following table shows what attributes will be present in the CSV.
| Created At (UTC) | Formatted as `YYYY-MM-DD HH:MM:SS` |
| Updated At (UTC) | Formatted as `YYYY-MM-DD HH:MM:SS` |
+In GitLab 14.7 and earlier, the first two columns were `MR ID` and `URL`,
+which [caused an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/34769)
+when importing back into GitLab.
+
## Limitations
- Export merge requests to CSV is not available at the Group's merge request list.
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 6fd57cda397..ff8a076465d 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -17,16 +17,16 @@ You can also open the Web IDE when viewing a file, from the repository file list
and from merge requests:
- *When viewing a file, or the repository file list* -
- 1. In the upper right corner of the page, select **Edit in Web IDE** if it is visible.
- 1. If **Edit in Web IDE** is not visible:
+ 1. In the upper right corner of the page, select **Open in Web IDE** if it is visible.
+ 1. If **Open in Web IDE** is not visible:
1. Select the **(angle-down)** next to **Edit** or **Gitpod**, depending on your configuration.
- 1. Select **Edit in Web IDE** from the list to display it as the editing option.
- 1. Select **Edit in Web IDE** to open the editor.
+ 1. Select **Open in Web IDE** from the list to display it as the editing option.
+ 1. Select **Open in Web IDE** to open the editor.
- *When viewing a merge request* -
1. Go to your merge request, and select the **Overview** tab.
1. Scroll to the widgets section, after the merge request description.
- 1. Select **Edit in Web IDE** if it is visible.
- 1. If **Edit in Web IDE** is not visible:
+ 1. Select **Open in Web IDE** if it is visible.
+ 1. If **Open in Web IDE** is not visible:
1. Select the **(angle-down)** next to **Open in Gitpod**.
1. Select **Open in Web IDE** from the list to display it as the editing option.
1. Select **Open in Web IDE** to open the editor.
@@ -221,7 +221,7 @@ different branch.
To edit Markdown files in the Web IDE:
1. Go to your repository, and navigate to the Markdown page you want to edit.
-1. Select **Edit in Web IDE**, and GitLab loads the page in a tab in the editor.
+1. Select **Open in Web IDE**, and GitLab loads the page in a tab in the editor.
1. Make your changes to the file. GitLab supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown).
1. When your changes are complete, select **Commit** in the left sidebar.
1. Add a commit message, select the branch you want to commit to, and select **Commit**.
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 9b32d285ec0..950119e155e 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -195,6 +195,16 @@ module Gitlab
MAX_TIMESTAMP_VALUE > timestamp ? timestamp : MAX_TIMESTAMP_VALUE.dup
end
+ def self.all_uncached(&block)
+ # Calls to #uncached only disable caching for the current connection. Since the load balancer
+ # can potentially upgrade from read to read-write mode (using a different connection), we specify
+ # up-front that we'll explicitly use the primary for the duration of the operation.
+ Gitlab::Database::LoadBalancing::Session.current.use_primary do
+ base_models = database_base_models.values
+ base_models.reduce(block) { |blk, model| -> { model.uncached(&blk) } }.call
+ end
+ end
+
def self.allow_cross_joins_across_databases(url:)
# this method is implemented in:
# spec/support/database/prevent_cross_joins.rb
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index d59be09f85e..f5d74ad1563 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -58,6 +58,7 @@ module Gitlab
push_frontend_feature_flag(:new_header_search, default_enabled: :yaml)
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
+ push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/import_export/group/relation_tree_restorer.rb b/lib/gitlab/import_export/group/relation_tree_restorer.rb
index fa994d06199..b44874f598c 100644
--- a/lib/gitlab/import_export/group/relation_tree_restorer.rb
+++ b/lib/gitlab/import_export/group/relation_tree_restorer.rb
@@ -29,7 +29,7 @@ module Gitlab
end
def restore
- ActiveRecord::Base.uncached do
+ Gitlab::Database.all_uncached do
ActiveRecord::Base.no_touching do
update_params!
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cb2427fe50a..39789cdfd6e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13229,9 +13229,6 @@ msgstr ""
msgid "Edit identity for %{user_name}"
msgstr ""
-msgid "Edit in Web IDE"
-msgstr ""
-
msgid "Edit in pipeline editor"
msgstr ""
@@ -31671,6 +31668,9 @@ msgstr ""
msgid "Runners|Never contacted"
msgstr ""
+msgid "Runners|New group runners view"
+msgstr ""
+
msgid "Runners|New registration token generated!"
msgstr ""
@@ -31737,6 +31737,9 @@ msgstr ""
msgid "Runners|Revision"
msgstr ""
+msgid "Runners|Rocket launch illustration"
+msgstr ""
+
msgid "Runners|Runner"
msgstr ""
@@ -31803,6 +31806,12 @@ msgstr ""
msgid "Runners|Tags"
msgstr ""
+msgid "Runners|Take me there!"
+msgstr ""
+
+msgid "Runners|The new view gives you more space and better visibility into your fleet of runners."
+msgstr ""
+
msgid "Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 27945950530..05acab5653f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -35,6 +35,8 @@ gem 'confiner', '~> 0.2'
gem 'chemlab', '~> 0.9'
gem 'chemlab-library-www-gitlab-com', '~> 0.1'
+gem "pact", "~> 1.12"
+
gem 'deprecation_toolkit', '~> 1.5.1', require: false
group :development do
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index f5dc995fea3..c18f0ad68be 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -28,6 +28,7 @@ GEM
require_all (>= 2, < 4)
uuid (>= 2.3, < 3)
ast (2.4.2)
+ awesome_print (1.9.2)
binding_ninja (0.2.3)
builder (3.2.4)
byebug (9.1.0)
@@ -91,6 +92,8 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
+ filelock (1.1.1)
+ find_a_port (1.0.1)
fog-core (2.1.0)
builder
excon (~> 0.58)
@@ -173,6 +176,7 @@ GEM
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb-client (1.17.0)
+ json (2.6.1)
jwt (2.3.0)
knapsack (4.0.0)
rake
@@ -205,6 +209,29 @@ GEM
sawyer (~> 0.8.0, >= 0.5.3)
oj (3.13.11)
os (1.1.4)
+ pact (1.59.0)
+ pact-mock_service (~> 3.0, >= 3.3.1)
+ pact-support (~> 1.15)
+ rack-test (>= 0.6.3, < 2.0.0)
+ rspec (~> 3.0)
+ term-ansicolor (~> 1.0)
+ thor (>= 0.20, < 2.0)
+ webrick (~> 1.3)
+ pact-mock_service (3.6.2)
+ filelock (~> 1.1)
+ find_a_port (~> 1.0.1)
+ json
+ pact-support (~> 1.12, >= 1.12.0)
+ rack (~> 2.0)
+ rspec (>= 2.14)
+ term-ansicolor (~> 1.0)
+ thor (>= 0.19, < 2.0)
+ webrick (~> 1.3)
+ pact-support (1.15.1)
+ awesome_print (~> 1.1)
+ randexp (~> 0.1.7)
+ rspec (>= 2.14)
+ term-ansicolor (~> 1.0)
parallel (1.19.2)
parallel_tests (2.29.0)
parallel
@@ -228,6 +255,7 @@ GEM
rack (>= 1.0, < 3)
rainbow (3.0.0)
rake (13.0.6)
+ randexp (0.1.7)
regexp_parser (2.1.1)
representable (3.1.1)
declarative (< 0.1.0)
@@ -282,12 +310,18 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
slack-notifier (2.4.0)
+ sync (0.5.0)
systemu (2.6.5)
table_print (1.5.7)
+ term-ansicolor (1.7.1)
+ tins (~> 1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
+ thor (1.2.1)
thread_safe (0.3.6)
timecop (0.9.1)
+ tins (1.31.0)
+ sync
trailblazer-option (0.1.2)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
@@ -337,6 +371,7 @@ DEPENDENCIES
influxdb-client (~> 1.17)
knapsack (~> 4.0)
octokit (~> 4.21)
+ pact (~> 1.12)
parallel (~> 1.19)
parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
diff --git a/qa/bin/contract b/qa/bin/contract
new file mode 100755
index 00000000000..f1bd7efc94d
--- /dev/null
+++ b/qa/bin/contract
@@ -0,0 +1,32 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require 'rake'
+
+host = ARGV.shift
+ENV['CONTRACT_HOST'] ||= host
+
+list = []
+
+loop do
+ keyword = ARGV.shift
+ case keyword
+ when '--mr'
+ ENV['CONTRACT_MR'] ||= ARGV.shift
+ list.push 'test:merge_request'
+ else
+ break
+ end
+end
+
+app = Rake.application
+
+Dir.chdir('contracts/provider') do
+ app.init
+ app.add_import 'Rakefile'
+ app.load_rakefile
+
+ list.each do |element|
+ app[element].invoke
+ end
+end
diff --git a/qa/contracts/.gitignore b/qa/contracts/.gitignore
new file mode 100644
index 00000000000..cb89d4102d3
--- /dev/null
+++ b/qa/contracts/.gitignore
@@ -0,0 +1,2 @@
+logs/
+consumer/node_modules
diff --git a/qa/contracts/consumer/.node-version b/qa/contracts/consumer/.node-version
new file mode 100644
index 00000000000..18711d290ea
--- /dev/null
+++ b/qa/contracts/consumer/.node-version
@@ -0,0 +1 @@
+14.17.5
diff --git a/qa/contracts/consumer/endpoints/merge_request.js b/qa/contracts/consumer/endpoints/merge_request.js
new file mode 100644
index 00000000000..74fd4e75bec
--- /dev/null
+++ b/qa/contracts/consumer/endpoints/merge_request.js
@@ -0,0 +1,42 @@
+'use strict';
+
+const axios = require('axios');
+
+exports.getMetadata = (endpoint) => {
+ const url = endpoint.url;
+
+ return axios
+ .request({
+ method: 'GET',
+ baseURL: url,
+ url: '/diffs_metadata.json',
+ headers: { Accept: '*/*' },
+ })
+ .then((response) => response.data);
+};
+
+exports.getDiscussions = (endpoint) => {
+ const url = endpoint.url;
+
+ return axios
+ .request({
+ method: 'GET',
+ baseURL: url,
+ url: '/discussions.json',
+ headers: { Accept: '*/*' },
+ })
+ .then((response) => response.data);
+};
+
+exports.getDiffs = (endpoint) => {
+ const url = endpoint.url;
+
+ return axios
+ .request({
+ method: 'GET',
+ baseURL: url,
+ url: '/diffs_batch.json?page=0',
+ headers: { Accept: '*/*' },
+ })
+ .then((response) => response.data);
+};
diff --git a/qa/contracts/consumer/fixtures/diffs.fixture.js b/qa/contracts/consumer/fixtures/diffs.fixture.js
new file mode 100644
index 00000000000..286d71f421c
--- /dev/null
+++ b/qa/contracts/consumer/fixtures/diffs.fixture.js
@@ -0,0 +1,89 @@
+'use strict';
+
+const { Matchers } = require('@pact-foundation/pact');
+
+const body = {
+ diff_files: Matchers.eachLike({
+ content_sha: Matchers.string('b0c94059db75b2473d616d4b1fde1a77533355a3'),
+ submodule: Matchers.boolean(false),
+ edit_path: Matchers.string('/gitlab-qa-bot/...'),
+ ide_edit_path: Matchers.string('/gitlab-qa-bot/...'),
+ old_path_html: Matchers.string('Gemfile'),
+ new_path_html: Matchers.string('Gemfile'),
+ blob: {
+ id: Matchers.string('855071bb3928d140764885964f7be1bb3e582495'),
+ path: Matchers.string('Gemfile'),
+ name: Matchers.string('Gemfile'),
+ mode: Matchers.string('1234567'),
+ readable_text: Matchers.boolean(true),
+ icon: Matchers.string('doc-text'),
+ },
+ can_modify_blob: Matchers.boolean(false),
+ file_identifier_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ file_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ file_path: Matchers.string('Gemfile'),
+ old_path: Matchers.string('Gemfile'),
+ new_path: Matchers.string('Gemfile'),
+ new_file: Matchers.boolean(false),
+ renamed_file: Matchers.boolean(false),
+ deleted_file: Matchers.boolean(false),
+ diff_refs: {
+ base_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ start_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ head_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ },
+ mode_changed: Matchers.boolean(false),
+ a_mode: Matchers.string('123456'),
+ b_mode: Matchers.string('123456'),
+ viewer: {
+ name: Matchers.string('text'),
+ collapsed: Matchers.boolean(false),
+ },
+ old_size: Matchers.integer(2288),
+ new_size: Matchers.integer(2288),
+ added_lines: Matchers.integer(1),
+ removed_lines: Matchers.integer(1),
+ load_collapsed_diff_url: Matchers.string('/gitlab-qa-bot/...'),
+ view_path: Matchers.string('/gitlab-qa-bot/...'),
+ context_lines_path: Matchers.string('/gitlab-qa-bot/...'),
+ highlighted_diff_lines: Matchers.eachLike({
+ // The following values can also be null which is not supported
+ //line_code: Matchers.string('de3150c01c3a946a6168173c4116741379fe3579_1_1'),
+ //old_line: Matchers.integer(1),
+ //new_line: Matchers.integer(1),
+ text: Matchers.string('source'),
+ rich_text: Matchers.string('<span></span>'),
+ can_receive_suggestion: Matchers.boolean(true),
+ }),
+ is_fully_expanded: Matchers.boolean(false),
+ }),
+ pagination: {
+ total_pages: Matchers.integer(1),
+ },
+};
+
+const Diffs = {
+ body: Matchers.extractPayload(body),
+
+ success: {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ body: body,
+ },
+
+ request: {
+ uponReceiving: 'a request for diff lines',
+ withRequest: {
+ method: 'GET',
+ path: '/diffs_batch.json',
+ headers: {
+ Accept: '*/*',
+ },
+ query: 'page=0',
+ },
+ },
+};
+
+exports.Diffs = Diffs;
diff --git a/qa/contracts/consumer/fixtures/discussions.fixture.js b/qa/contracts/consumer/fixtures/discussions.fixture.js
new file mode 100644
index 00000000000..cfc6112561b
--- /dev/null
+++ b/qa/contracts/consumer/fixtures/discussions.fixture.js
@@ -0,0 +1,85 @@
+'use strict';
+
+const { Matchers } = require('@pact-foundation/pact');
+
+const body = Matchers.eachLike({
+ id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
+ reply_id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
+ project_id: Matchers.integer(6954442),
+ confidential: Matchers.boolean(false),
+ diff_discussion: Matchers.boolean(false),
+ expanded: Matchers.boolean(false),
+ for_commit: Matchers.boolean(false),
+ individual_note: Matchers.boolean(true),
+ resolvable: Matchers.boolean(false),
+ resolved_by_push: Matchers.boolean(false),
+ notes: Matchers.eachLike({
+ id: Matchers.string('76489845'),
+ author: {
+ id: Matchers.integer(1675733),
+ username: Matchers.string('gitlab-qa-bot'),
+ name: Matchers.string('gitlab-qa-bot'),
+ state: Matchers.string('active'),
+ avatar_url: Matchers.string(
+ 'https://secure.gravatar.com/avatar/8355ad0f2761367fae6b9c4fe80994b9?s=80&d=identicon',
+ ),
+ show_status: Matchers.boolean(false),
+ path: Matchers.string('/gitlab-qa-bot'),
+ },
+ created_at: Matchers.iso8601DateTimeWithMillis('2022-02-22T07:06:55.038Z'),
+ updated_at: Matchers.iso8601DateTimeWithMillis('2022-02-22T07:06:55.038Z'),
+ system: Matchers.boolean(false),
+ noteable_id: Matchers.integer(8333422),
+ noteable_type: Matchers.string('MergeRequest'),
+ resolvable: Matchers.boolean(false),
+ resolved: Matchers.boolean(true),
+ confidential: Matchers.boolean(false),
+ noteable_iid: Matchers.integer(1),
+ note: Matchers.string('This is a test comment'),
+ note_html: Matchers.string(
+ '<p data-sourcepos="1:1-1:22" dir="auto">This is a test comment</p>',
+ ),
+ current_user: {
+ can_edit: Matchers.boolean(true),
+ can_award_emoji: Matchers.boolean(true),
+ can_resolve: Matchers.boolean(false),
+ can_resolve_discussion: Matchers.boolean(false),
+ },
+ is_noteable_author: Matchers.boolean(true),
+ discussion_id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
+ emoji_awardable: Matchers.boolean(true),
+ report_abuse_path: Matchers.string('/gitlab-qa-bot/...'),
+ noteable_note_url: Matchers.string('https://staging.gitlab.com/gitlab-qa-bot/...'),
+ cached_markdown_version: Matchers.integer(1900552),
+ human_access: Matchers.string('Maintainer'),
+ is_contributor: Matchers.boolean(false),
+ project_name: Matchers.string('contract-testing'),
+ path: Matchers.string('/gitlab-qa-bot/...'),
+ }),
+ resolved: Matchers.boolean(true),
+});
+
+const Discussions = {
+ body: Matchers.extractPayload(body),
+
+ success: {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ body: body,
+ },
+
+ request: {
+ uponReceiving: 'a request for discussions',
+ withRequest: {
+ method: 'GET',
+ path: '/discussions.json',
+ headers: {
+ Accept: '*/*',
+ },
+ },
+ },
+};
+
+exports.Discussions = Discussions;
diff --git a/qa/contracts/consumer/fixtures/metadata.fixture.js b/qa/contracts/consumer/fixtures/metadata.fixture.js
new file mode 100644
index 00000000000..05a4831c447
--- /dev/null
+++ b/qa/contracts/consumer/fixtures/metadata.fixture.js
@@ -0,0 +1,96 @@
+'use strict';
+
+const { Matchers } = require('@pact-foundation/pact');
+
+const body = {
+ real_size: Matchers.string('1'),
+ size: Matchers.integer(1),
+ branch_name: Matchers.string('testing-branch-1'),
+ source_branch_exists: Matchers.boolean(true),
+ target_branch_name: Matchers.string('master'),
+ merge_request_diff: {
+ created_at: Matchers.iso8601DateTimeWithMillis('2022-02-17T11:47:08.804Z'),
+ commits_count: Matchers.integer(1),
+ latest: Matchers.boolean(true),
+ short_commit_sha: Matchers.string('aee1ffec'),
+ base_version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
+ ),
+ head_version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true',
+ ),
+ version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
+ ),
+ compare_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f',
+ ),
+ },
+ latest_diff: Matchers.boolean(true),
+ latest_version_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs'),
+ added_lines: Matchers.integer(1),
+ removed_lines: Matchers.integer(1),
+ render_overflow_warning: Matchers.boolean(false),
+ email_patch_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1.patch'),
+ plain_diff_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1.diff'),
+ merge_request_diffs: Matchers.eachLike({
+ commits_count: Matchers.integer(1),
+ latest: Matchers.boolean(true),
+ short_commit_sha: Matchers.string('aee1ffec'),
+ base_version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
+ ),
+ head_version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true',
+ ),
+ version_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
+ ),
+ compare_path: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f',
+ ),
+ }),
+ definition_path_prefix: Matchers.string(
+ '/gitlab-qa-bot/contract-testing/-/blob/aee1ffec2299c0cfb17c8821e931339b73a3759f',
+ ),
+ diff_files: Matchers.eachLike({
+ added_lines: Matchers.integer(1),
+ removed_lines: Matchers.integer(1),
+ new_path: Matchers.string('Gemfile'),
+ old_path: Matchers.string('Gemfile'),
+ new_file: Matchers.boolean(false),
+ deleted_file: Matchers.boolean(false),
+ submodule: Matchers.boolean(false),
+ file_identifier_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
+ file_hash: Matchers.string('de3150c01c3a946a6168173c4116741379fe3579'),
+ }),
+ has_conflicts: Matchers.boolean(false),
+ can_merge: Matchers.boolean(false),
+ project_path: Matchers.string('gitlab-qa-bot/contract-testing'),
+ project_name: Matchers.string('contract-testing'),
+};
+
+const Metadata = {
+ body: Matchers.extractPayload(body),
+
+ success: {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ body: body,
+ },
+
+ request: {
+ uponReceiving: 'a request for Metadata',
+ withRequest: {
+ method: 'GET',
+ path: '/diffs_metadata.json',
+ headers: {
+ Accept: '*/*',
+ },
+ },
+ },
+};
+
+exports.Metadata = Metadata;
diff --git a/qa/contracts/consumer/package.json b/qa/contracts/consumer/package.json
new file mode 100644
index 00000000000..b4a3f59e89e
--- /dev/null
+++ b/qa/contracts/consumer/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "consumer",
+ "version": "1.0.0",
+ "description": "consumer side contract testing",
+ "license": "MIT",
+ "repository": "https://gitlab.com/gitlab-org/gitlab.git",
+ "dependencies": {
+ "@pact-foundation/pact": "^9.17.2",
+ "axios": "^0.26.0",
+ "jest": "^27.5.1",
+ "jest-pact": "^0.9.1",
+ "prettier": "^2.5.1"
+ },
+ "scripts": {
+ "test": "jest specs/ --runInBand"
+ }
+}
diff --git a/qa/contracts/consumer/specs/diffs.spec.js b/qa/contracts/consumer/specs/diffs.spec.js
new file mode 100644
index 00000000000..5be2ed7ac00
--- /dev/null
+++ b/qa/contracts/consumer/specs/diffs.spec.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const { pactWith } = require('jest-pact');
+
+const { Diffs } = require('../fixtures/diffs.fixture');
+const { getDiffs } = require('../endpoints/merge_request');
+
+pactWith(
+ {
+ consumer: 'Merge Request Page',
+ provider: 'Merge Request Diffs Endpoint',
+ log: '../logs/consumer.log',
+ dir: '../contracts',
+ },
+
+ (provider) => {
+ describe('Diffs Endpoint', () => {
+ beforeEach(() => {
+ const interaction = {
+ ...Diffs.request,
+ willRespondWith: Diffs.success,
+ };
+ return provider.addInteraction(interaction);
+ });
+
+ it('return a successful body', () => {
+ return getDiffs({
+ url: provider.mockService.baseUrl,
+ }).then((diffs) => {
+ expect(diffs).toEqual(Diffs.body);
+ });
+ });
+ });
+ },
+);
diff --git a/qa/contracts/consumer/specs/discussions.spec.js b/qa/contracts/consumer/specs/discussions.spec.js
new file mode 100644
index 00000000000..28ee3186a9f
--- /dev/null
+++ b/qa/contracts/consumer/specs/discussions.spec.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const { pactWith } = require('jest-pact');
+
+const { Discussions } = require('../fixtures/discussions.fixture');
+const { getDiscussions } = require('../endpoints/merge_request');
+
+pactWith(
+ {
+ consumer: 'Merge Request Page',
+ provider: 'Merge Request Discussions Endpoint',
+ log: '../logs/consumer.log',
+ dir: '../contracts',
+ },
+
+ (provider) => {
+ describe('Discussions Endpoint', () => {
+ beforeEach(() => {
+ const interaction = {
+ ...Discussions.request,
+ willRespondWith: Discussions.success,
+ };
+ return provider.addInteraction(interaction);
+ });
+
+ it('return a successful body', () => {
+ return getDiscussions({
+ url: provider.mockService.baseUrl,
+ }).then((discussions) => {
+ expect(discussions).toEqual(Discussions.body);
+ });
+ });
+ });
+ },
+);
diff --git a/qa/contracts/consumer/specs/metadata.spec.js b/qa/contracts/consumer/specs/metadata.spec.js
new file mode 100644
index 00000000000..31fc398f228
--- /dev/null
+++ b/qa/contracts/consumer/specs/metadata.spec.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const { pactWith } = require('jest-pact');
+
+const { Metadata } = require('../fixtures/metadata.fixture');
+const { getMetadata } = require('../endpoints/merge_request');
+
+pactWith(
+ {
+ consumer: 'Merge Request Page',
+ provider: 'Merge Request Metadata Endpoint',
+ log: '../logs/consumer.log',
+ dir: '../contracts',
+ },
+
+ (provider) => {
+ describe('Metadata Endpoint', () => {
+ beforeEach(() => {
+ const interaction = {
+ ...Metadata.request,
+ willRespondWith: Metadata.success,
+ };
+ return provider.addInteraction(interaction);
+ });
+
+ it('return a successful body', () => {
+ return getMetadata({
+ url: provider.mockService.baseUrl,
+ }).then((metadata) => {
+ expect(metadata).toEqual(Metadata.body);
+ });
+ });
+ });
+ },
+);
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json
new file mode 100644
index 00000000000..8df54c25326
--- /dev/null
+++ b/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json
@@ -0,0 +1,228 @@
+{
+ "consumer": {
+ "name": "Merge Request Page"
+ },
+ "provider": {
+ "name": "Merge Request Diffs Endpoint"
+ },
+ "interactions": [
+ {
+ "description": "a request for diff lines",
+ "request": {
+ "method": "GET",
+ "path": "/diffs_batch.json",
+ "query": "page=0",
+ "headers": {
+ "Accept": "*/*"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json; charset=utf-8"
+ },
+ "body": {
+ "diff_files": [
+ {
+ "content_sha": "b0c94059db75b2473d616d4b1fde1a77533355a3",
+ "submodule": false,
+ "edit_path": "/gitlab-qa-bot/...",
+ "ide_edit_path": "/gitlab-qa-bot/...",
+ "old_path_html": "Gemfile",
+ "new_path_html": "Gemfile",
+ "blob": {
+ "id": "855071bb3928d140764885964f7be1bb3e582495",
+ "path": "Gemfile",
+ "name": "Gemfile",
+ "mode": "1234567",
+ "readable_text": true,
+ "icon": "doc-text"
+ },
+ "can_modify_blob": false,
+ "file_identifier_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
+ "file_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
+ "file_path": "Gemfile",
+ "old_path": "Gemfile",
+ "new_path": "Gemfile",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false,
+ "diff_refs": {
+ "base_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
+ "start_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
+ "head_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587"
+ },
+ "mode_changed": false,
+ "a_mode": "123456",
+ "b_mode": "123456",
+ "viewer": {
+ "name": "text",
+ "collapsed": false
+ },
+ "old_size": 2288,
+ "new_size": 2288,
+ "added_lines": 1,
+ "removed_lines": 1,
+ "load_collapsed_diff_url": "/gitlab-qa-bot/...",
+ "view_path": "/gitlab-qa-bot/...",
+ "context_lines_path": "/gitlab-qa-bot/...",
+ "highlighted_diff_lines": [
+ {
+ "text": "source",
+ "rich_text": "<span></span>",
+ "can_receive_suggestion": true
+ }
+ ],
+ "is_fully_expanded": false
+ }
+ ],
+ "pagination": {
+ "total_pages": 1
+ }
+ },
+ "matchingRules": {
+ "$.body.diff_files": {
+ "min": 1
+ },
+ "$.body.diff_files[*].*": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].content_sha": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].submodule": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].edit_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].ide_edit_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].old_path_html": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_path_html": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.id": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.name": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.mode": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.readable_text": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].blob.icon": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].can_modify_blob": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].file_identifier_hash": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].file_hash": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].file_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].old_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_file": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].renamed_file": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].deleted_file": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].diff_refs.base_sha": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].diff_refs.start_sha": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].diff_refs.head_sha": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].mode_changed": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].a_mode": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].b_mode": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].viewer.name": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].viewer.collapsed": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].old_size": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_size": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].added_lines": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].removed_lines": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].load_collapsed_diff_url": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].view_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].context_lines_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].highlighted_diff_lines": {
+ "min": 1
+ },
+ "$.body.diff_files[*].highlighted_diff_lines[*].*": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].highlighted_diff_lines[*].text": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].highlighted_diff_lines[*].rich_text": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].highlighted_diff_lines[*].can_receive_suggestion": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].is_fully_expanded": {
+ "match": "type"
+ },
+ "$.body.pagination.total_pages": {
+ "match": "type"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pactSpecification": {
+ "version": "2.0.0"
+ }
+ }
+} \ No newline at end of file
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json
new file mode 100644
index 00000000000..14839053e57
--- /dev/null
+++ b/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json
@@ -0,0 +1,235 @@
+{
+ "consumer": {
+ "name": "Merge Request Page"
+ },
+ "provider": {
+ "name": "Merge Request Discussions Endpoint"
+ },
+ "interactions": [
+ {
+ "description": "a request for discussions",
+ "request": {
+ "method": "GET",
+ "path": "/discussions.json",
+ "headers": {
+ "Accept": "*/*"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json; charset=utf-8"
+ },
+ "body": [
+ {
+ "id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
+ "reply_id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
+ "project_id": 6954442,
+ "confidential": false,
+ "diff_discussion": false,
+ "expanded": false,
+ "for_commit": false,
+ "individual_note": true,
+ "resolvable": false,
+ "resolved_by_push": false,
+ "notes": [
+ {
+ "id": "76489845",
+ "author": {
+ "id": 1675733,
+ "username": "gitlab-qa-bot",
+ "name": "gitlab-qa-bot",
+ "state": "active",
+ "avatar_url": "https://secure.gravatar.com/avatar/8355ad0f2761367fae6b9c4fe80994b9?s=80&d=identicon",
+ "show_status": false,
+ "path": "/gitlab-qa-bot"
+ },
+ "created_at": "2022-02-22T07:06:55.038Z",
+ "updated_at": "2022-02-22T07:06:55.038Z",
+ "system": false,
+ "noteable_id": 8333422,
+ "noteable_type": "MergeRequest",
+ "resolvable": false,
+ "resolved": true,
+ "confidential": false,
+ "noteable_iid": 1,
+ "note": "This is a test comment",
+ "note_html": "<p data-sourcepos=\"1:1-1:22\" dir=\"auto\">This is a test comment</p>",
+ "current_user": {
+ "can_edit": true,
+ "can_award_emoji": true,
+ "can_resolve": false,
+ "can_resolve_discussion": false
+ },
+ "is_noteable_author": true,
+ "discussion_id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
+ "emoji_awardable": true,
+ "report_abuse_path": "/gitlab-qa-bot/...",
+ "noteable_note_url": "https://staging.gitlab.com/gitlab-qa-bot/...",
+ "cached_markdown_version": 1900552,
+ "human_access": "Maintainer",
+ "is_contributor": false,
+ "project_name": "contract-testing",
+ "path": "/gitlab-qa-bot/..."
+ }
+ ],
+ "resolved": true
+ }
+ ],
+ "matchingRules": {
+ "$.body": {
+ "min": 1
+ },
+ "$.body[*].*": {
+ "match": "type"
+ },
+ "$.body[*].id": {
+ "match": "type"
+ },
+ "$.body[*].reply_id": {
+ "match": "type"
+ },
+ "$.body[*].project_id": {
+ "match": "type"
+ },
+ "$.body[*].confidential": {
+ "match": "type"
+ },
+ "$.body[*].diff_discussion": {
+ "match": "type"
+ },
+ "$.body[*].expanded": {
+ "match": "type"
+ },
+ "$.body[*].for_commit": {
+ "match": "type"
+ },
+ "$.body[*].individual_note": {
+ "match": "type"
+ },
+ "$.body[*].resolvable": {
+ "match": "type"
+ },
+ "$.body[*].resolved_by_push": {
+ "match": "type"
+ },
+ "$.body[*].notes": {
+ "min": 1
+ },
+ "$.body[*].notes[*].*": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].id": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.id": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.username": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.name": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.state": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.avatar_url": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.show_status": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].author.path": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].created_at": {
+ "match": "regex",
+ "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
+ },
+ "$.body[*].notes[*].updated_at": {
+ "match": "regex",
+ "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
+ },
+ "$.body[*].notes[*].system": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].noteable_id": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].noteable_type": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].resolvable": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].resolved": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].confidential": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].noteable_iid": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].note": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].note_html": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].current_user.can_edit": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].current_user.can_award_emoji": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].current_user.can_resolve": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].current_user.can_resolve_discussion": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].is_noteable_author": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].discussion_id": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].emoji_awardable": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].report_abuse_path": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].noteable_note_url": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].cached_markdown_version": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].human_access": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].is_contributor": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].project_name": {
+ "match": "type"
+ },
+ "$.body[*].notes[*].path": {
+ "match": "type"
+ },
+ "$.body[*].resolved": {
+ "match": "type"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pactSpecification": {
+ "version": "2.0.0"
+ }
+ }
+} \ No newline at end of file
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json
new file mode 100644
index 00000000000..4b6cab0fc94
--- /dev/null
+++ b/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json
@@ -0,0 +1,222 @@
+{
+ "consumer": {
+ "name": "Merge Request Page"
+ },
+ "provider": {
+ "name": "Merge Request Metadata Endpoint"
+ },
+ "interactions": [
+ {
+ "description": "a request for Metadata",
+ "request": {
+ "method": "GET",
+ "path": "/diffs_metadata.json",
+ "headers": {
+ "Accept": "*/*"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json; charset=utf-8"
+ },
+ "body": {
+ "real_size": "1",
+ "size": 1,
+ "branch_name": "testing-branch-1",
+ "source_branch_exists": true,
+ "target_branch_name": "master",
+ "merge_request_diff": {
+ "created_at": "2022-02-17T11:47:08.804Z",
+ "commits_count": 1,
+ "latest": true,
+ "short_commit_sha": "aee1ffec",
+ "base_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
+ "head_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true",
+ "version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
+ "compare_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f"
+ },
+ "latest_diff": true,
+ "latest_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs",
+ "added_lines": 1,
+ "removed_lines": 1,
+ "render_overflow_warning": false,
+ "email_patch_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1.patch",
+ "plain_diff_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1.diff",
+ "merge_request_diffs": [
+ {
+ "commits_count": 1,
+ "latest": true,
+ "short_commit_sha": "aee1ffec",
+ "base_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
+ "head_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true",
+ "version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
+ "compare_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f"
+ }
+ ],
+ "definition_path_prefix": "/gitlab-qa-bot/contract-testing/-/blob/aee1ffec2299c0cfb17c8821e931339b73a3759f",
+ "diff_files": [
+ {
+ "added_lines": 1,
+ "removed_lines": 1,
+ "new_path": "Gemfile",
+ "old_path": "Gemfile",
+ "new_file": false,
+ "deleted_file": false,
+ "submodule": false,
+ "file_identifier_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
+ "file_hash": "de3150c01c3a946a6168173c4116741379fe3579"
+ }
+ ],
+ "has_conflicts": false,
+ "can_merge": false,
+ "project_path": "gitlab-qa-bot/contract-testing",
+ "project_name": "contract-testing"
+ },
+ "matchingRules": {
+ "$.body.real_size": {
+ "match": "type"
+ },
+ "$.body.size": {
+ "match": "type"
+ },
+ "$.body.branch_name": {
+ "match": "type"
+ },
+ "$.body.source_branch_exists": {
+ "match": "type"
+ },
+ "$.body.target_branch_name": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.created_at": {
+ "match": "regex",
+ "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
+ },
+ "$.body.merge_request_diff.commits_count": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.latest": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.short_commit_sha": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.base_version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.head_version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diff.compare_path": {
+ "match": "type"
+ },
+ "$.body.latest_diff": {
+ "match": "type"
+ },
+ "$.body.latest_version_path": {
+ "match": "type"
+ },
+ "$.body.added_lines": {
+ "match": "type"
+ },
+ "$.body.removed_lines": {
+ "match": "type"
+ },
+ "$.body.render_overflow_warning": {
+ "match": "type"
+ },
+ "$.body.email_patch_path": {
+ "match": "type"
+ },
+ "$.body.plain_diff_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs": {
+ "min": 1
+ },
+ "$.body.merge_request_diffs[*].*": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].commits_count": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].latest": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].short_commit_sha": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].base_version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].head_version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].version_path": {
+ "match": "type"
+ },
+ "$.body.merge_request_diffs[*].compare_path": {
+ "match": "type"
+ },
+ "$.body.definition_path_prefix": {
+ "match": "type"
+ },
+ "$.body.diff_files": {
+ "min": 1
+ },
+ "$.body.diff_files[*].*": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].added_lines": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].removed_lines": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].old_path": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].new_file": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].deleted_file": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].submodule": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].file_identifier_hash": {
+ "match": "type"
+ },
+ "$.body.diff_files[*].file_hash": {
+ "match": "type"
+ },
+ "$.body.has_conflicts": {
+ "match": "type"
+ },
+ "$.body.can_merge": {
+ "match": "type"
+ },
+ "$.body.project_path": {
+ "match": "type"
+ },
+ "$.body.project_name": {
+ "match": "type"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pactSpecification": {
+ "version": "2.0.0"
+ }
+ }
+} \ No newline at end of file
diff --git a/qa/contracts/provider/Rakefile b/qa/contracts/provider/Rakefile
new file mode 100644
index 00000000000..00ca2355b11
--- /dev/null
+++ b/qa/contracts/provider/Rakefile
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'pact/tasks/verification_task'
+
+Pact::VerificationTask.new(:metadata) do |pact|
+ pact.uri '../contracts/merge_request_page-merge_request_metadata_endpoint.json', pact_helper: './spec/metadata_helper.rb'
+end
+
+Pact::VerificationTask.new(:discussions) do |pact|
+ pact.uri '../contracts/merge_request_page-merge_request_discussions_endpoint.json', pact_helper: './spec/discussions_helper.rb'
+end
+
+Pact::VerificationTask.new(:diffs) do |pact|
+ pact.uri '../contracts/merge_request_page-merge_request_diffs_endpoint.json', pact_helper: './spec/diffs_helper.rb'
+end
+
+task 'test:merge_request' => ['pact:verify:metadata', 'pact:verify:discussions', 'pact:verify:diffs']
diff --git a/qa/contracts/provider/environments/base.rb b/qa/contracts/provider/environments/base.rb
new file mode 100644
index 00000000000..36b97357417
--- /dev/null
+++ b/qa/contracts/provider/environments/base.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'faraday'
+
+module Environments
+ class Base
+ attr_writer :base_url, :merge_request
+
+ def call(env)
+ @payload
+ end
+
+ def http(endpoint)
+ Faraday.default_adapter = :net_http
+ response = Faraday.get(@base_url + endpoint)
+ @payload = [response.status, response.headers, [response.body]]
+ self
+ end
+
+ def merge_request(endpoint)
+ if endpoint.include? '.json'
+ http(@merge_request + endpoint)
+ end
+ end
+ end
+end
diff --git a/qa/contracts/provider/environments/local.rb b/qa/contracts/provider/environments/local.rb
new file mode 100644
index 00000000000..7721122a097
--- /dev/null
+++ b/qa/contracts/provider/environments/local.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require_relative './base'
+
+module Environments
+ class Local < Base
+ def initialize
+ @base_url = ENV['CONTRACT_HOST']
+ @merge_request = ENV['CONTRACT_MR']
+ end
+ end
+end
diff --git a/qa/contracts/provider/spec/diffs_helper.rb b/qa/contracts/provider/spec/diffs_helper.rb
new file mode 100644
index 00000000000..4c5583c5011
--- /dev/null
+++ b/qa/contracts/provider/spec/diffs_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative '../environments/local'
+
+module DiffsHelper
+ local = Environments::Local.new
+
+ Pact.service_provider "Merge Request Diffs Endpoint" do
+ app { local.merge_request('/diffs_batch.json?page=0') }
+
+ honours_pact_with 'Merge Request Page' do
+ pact_uri '../contracts/merge_request_page-merge_request_diffs_endpoint.json'
+ end
+ end
+end
diff --git a/qa/contracts/provider/spec/discussions_helper.rb b/qa/contracts/provider/spec/discussions_helper.rb
new file mode 100644
index 00000000000..44f9803989f
--- /dev/null
+++ b/qa/contracts/provider/spec/discussions_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative '../environments/local'
+
+module DiscussionsHelper
+ local = Environments::Local.new
+
+ Pact.service_provider "Merge Request Discussions Endpoint" do
+ app { local.merge_request('/discussions.json') }
+
+ honours_pact_with 'Merge Request Page' do
+ pact_uri '../contracts/merge_request_page-merge_request_discussions_endpoint.json'
+ end
+ end
+end
diff --git a/qa/contracts/provider/spec/metadata_helper.rb b/qa/contracts/provider/spec/metadata_helper.rb
new file mode 100644
index 00000000000..ac2910b1158
--- /dev/null
+++ b/qa/contracts/provider/spec/metadata_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative '../environments/local'
+
+module MetadataHelper
+ local = Environments::Local.new
+
+ Pact.service_provider "Merge Request Metadata Endpoint" do
+ app { local.merge_request('/diffs_metadata.json') }
+
+ honours_pact_with 'Merge Request Page' do
+ pact_uri '../contracts/merge_request_page-merge_request_metadata_endpoint.json'
+ end
+ end
+end
diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb
index b059cd8da29..8851aeb6381 100644
--- a/spec/features/groups/settings/ci_cd_spec.rb
+++ b/spec/features/groups/settings/ci_cd_spec.rb
@@ -13,6 +13,24 @@ RSpec.describe 'Group CI/CD settings' do
sign_in(user)
end
+ describe 'new group runners view banner' do
+ it 'displays banner' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).to have_content(s_('Runners|New group runners view'))
+ expect(page).to have_link(href: group_runners_path(group))
+ end
+
+ it 'does not display banner' do
+ stub_feature_flags(runner_list_group_view_vue_ui: false)
+
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).not_to have_content(s_('Runners|New group runners view'))
+ expect(page).not_to have_link(href: group_runners_path(group))
+ end
+ end
+
describe 'runners registration token' do
let!(:token) { group.runners_token }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b3b7c81e9e7..82b91390027 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -279,6 +279,46 @@ RSpec.describe Gitlab::Database do
end
end
+ describe '.all_uncached' do
+ let(:base_model) do
+ Class.new do
+ def self.uncached
+ @uncached = true
+
+ yield
+ end
+ end
+ end
+
+ let(:model1) { Class.new(base_model) }
+ let(:model2) { Class.new(base_model) }
+
+ before do
+ allow(described_class).to receive(:database_base_models)
+ .and_return({ model1: model1, model2: model2 }.with_indifferent_access)
+ end
+
+ it 'wraps the given block in uncached calls for each primary connection', :aggregate_failures do
+ expect(model1.instance_variable_get(:@uncached)).to be_nil
+ expect(model2.instance_variable_get(:@uncached)).to be_nil
+
+ expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary).and_yield
+
+ expect(model2).to receive(:uncached).and_call_original
+ expect(model1).to receive(:uncached).and_call_original
+
+ yielded_to_block = false
+ described_class.all_uncached do
+ expect(model1.instance_variable_get(:@uncached)).to be(true)
+ expect(model2.instance_variable_get(:@uncached)).to be(true)
+
+ yielded_to_block = true
+ end
+
+ expect(yielded_to_block).to be(true)
+ end
+ end
+
describe '.read_only?' do
it 'returns false' do
expect(described_class.read_only?).to eq(false)
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 482e372543c..dd954e08156 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -432,6 +432,12 @@ RSpec.describe WebHook do
expect(hook).not_to be_temporarily_disabled
end
+
+ it 'can ignore the feature flag' do
+ stub_feature_flags(web_hooks_disable_failed: false)
+
+ expect(hook).to be_temporarily_disabled(ignore_flag: true)
+ end
end
end
@@ -454,6 +460,12 @@ RSpec.describe WebHook do
expect(hook).not_to be_permanently_disabled
end
+
+ it 'can ignore the feature flag' do
+ stub_feature_flags(web_hooks_disable_failed: false)
+
+ expect(hook).to be_permanently_disabled(ignore_flag: true)
+ end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 793b1fffd5f..0da37fc5378 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1755,4 +1755,100 @@ RSpec.describe ProjectPolicy do
end
end
end
+
+ describe 'register_project_runners' do
+ context 'admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ context 'with runner_registration_control FF disabled' do
+ before do
+ stub_feature_flags(runner_registration_control: false)
+ end
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+ end
+
+ context 'with runner_registration_control FF enabled' do
+ before do
+ stub_feature_flags(runner_registration_control: true)
+ end
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+
+ context 'with project runner registration disabled' do
+ before do
+ stub_application_setting(valid_runner_registrars: ['group'])
+ end
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+ end
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+ end
+
+ context 'with owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+
+ context 'with runner_registration_control FF disabled' do
+ before do
+ stub_feature_flags(runner_registration_control: false)
+ end
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+ end
+
+ context 'with runner_registration_control FF enabled' do
+ before do
+ stub_feature_flags(runner_registration_control: true)
+ end
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+
+ context 'with project runner registration disabled' do
+ before do
+ stub_application_setting(valid_runner_registrars: ['group'])
+ end
+
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+ end
+ end
+
+ context 'with maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:register_project_runners) }
+ end
+
+ context 'with reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+
+ context 'with guest' do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+
+ context 'with non member' do
+ let(:current_user) { create(:user) }
+
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+
+ context 'with anonymous' do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_disallowed(:register_project_runners) }
+ end
+ end
end
diff --git a/spec/services/web_hooks/log_execution_service_spec.rb b/spec/services/web_hooks/log_execution_service_spec.rb
index 7e9a8de2dee..0ba0372b99d 100644
--- a/spec/services/web_hooks/log_execution_service_spec.rb
+++ b/spec/services/web_hooks/log_execution_service_spec.rb
@@ -95,12 +95,37 @@ RSpec.describe WebHooks::LogExecutionService do
it 'resets the failure count' do
expect { service.execute }.to change(project_hook, :recent_failures).to(0)
end
+
+ it 'sends a message to AuthLogger if the hook as not previously enabled' do
+ project_hook.update!(recent_failures: ::WebHook::FAILURE_THRESHOLD + 1)
+
+ expect(Gitlab::AuthLogger).to receive(:info).with include(
+ message: 'WebHook change active_state',
+ # identification
+ hook_id: project_hook.id,
+ hook_type: project_hook.type,
+ project_id: project_hook.project_id,
+ group_id: nil,
+ # relevant data
+ prev_state: :permanently_disabled,
+ new_state: :enabled,
+ duration: 1.2,
+ response_status: '200',
+ recent_hook_failures: 0
+ )
+
+ service.execute
+ end
end
end
context 'when response_category is :failed' do
let(:response_category) { :failed }
+ before do
+ data[:response_status] = '400'
+ end
+
it 'increments the failure count' do
expect { service.execute }.to change(project_hook, :recent_failures).by(1)
end
@@ -127,11 +152,36 @@ RSpec.describe WebHooks::LogExecutionService do
expect { service.execute }.not_to change(project_hook, :recent_failures)
end
end
+
+ it 'sends a message to AuthLogger if the state would change' do
+ project_hook.update!(recent_failures: ::WebHook::FAILURE_THRESHOLD)
+
+ expect(Gitlab::AuthLogger).to receive(:info).with include(
+ message: 'WebHook change active_state',
+ # identification
+ hook_id: project_hook.id,
+ hook_type: project_hook.type,
+ project_id: project_hook.project_id,
+ group_id: nil,
+ # relevant data
+ prev_state: :enabled,
+ new_state: :permanently_disabled,
+ duration: (be > 0),
+ response_status: data[:response_status],
+ recent_hook_failures: ::WebHook::FAILURE_THRESHOLD + 1
+ )
+
+ service.execute
+ end
end
context 'when response_category is :error' do
let(:response_category) { :error }
+ before do
+ data[:response_status] = '500'
+ end
+
it 'does not increment the failure count' do
expect { service.execute }.not_to change(project_hook, :recent_failures)
end
@@ -144,6 +194,25 @@ RSpec.describe WebHooks::LogExecutionService do
expect { service.execute }.to change(project_hook, :backoff_count).by(1)
end
+ it 'sends a message to AuthLogger if the state would change' do
+ expect(Gitlab::AuthLogger).to receive(:info).with include(
+ message: 'WebHook change active_state',
+ # identification
+ hook_id: project_hook.id,
+ hook_type: project_hook.type,
+ project_id: project_hook.project_id,
+ group_id: nil,
+ # relevant data
+ prev_state: :enabled,
+ new_state: :temporarily_disabled,
+ duration: (be > 0),
+ response_status: data[:response_status],
+ recent_hook_failures: 0
+ )
+
+ service.execute
+ end
+
context 'when the previous cool-off was near the maximum' do
before do
project_hook.update!(disabled_until: 5.minutes.ago, backoff_count: 8)
diff --git a/spec/views/projects/runners/_specific_runners.html.haml_spec.rb b/spec/views/projects/runners/_specific_runners.html.haml_spec.rb
index ace3502dd1e..ce16e0d5ac6 100644
--- a/spec/views/projects/runners/_specific_runners.html.haml_spec.rb
+++ b/spec/views/projects/runners/_specific_runners.html.haml_spec.rb
@@ -11,12 +11,14 @@ RSpec.describe 'projects/runners/specific_runners.html.haml' do
@project = project
@assignable_runners = []
@project_runners = []
+ allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:reset_registration_token_namespace_project_settings_ci_cd_path).and_return('banana_url')
end
context 'when project runner registration is allowed' do
before do
stub_application_setting(valid_runner_registrars: ['project'])
+ allow(view).to receive(:can?).with(user, :register_project_runners, project).and_return(true)
end
it 'enables the Remove project button for a project' do
@@ -32,7 +34,7 @@ RSpec.describe 'projects/runners/specific_runners.html.haml' do
stub_application_setting(valid_runner_registrars: ['group'])
end
- it 'does not enable the the Remove project button for a project' do
+ it 'does not enable the Remove project button for a project' do
render 'projects/runners/specific_runners', project: project
expect(rendered).to have_content 'Please contact an admin to register runners.'