From 6728ed6fe203d0613ee63c89a08a70fffb93405c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 8 Mar 2022 12:20:17 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .eslintignore | 1 + .rubocop_todo/database/multiple_databases.yml | 1 - .../diffs/components/diff_file_header.vue | 2 +- .../keep_around_refs_created_event.rb | 14 ++ app/models/hooks/web_hook.rb | 15 +- app/policies/project_policy.rb | 11 + app/services/issues/export_csv_service.rb | 4 +- .../merge_requests/export_csv_service.rb | 4 +- .../web_hooks/log_execution_service.rb | 30 ++- app/views/dashboard/_activities.html.haml | 2 +- app/views/dashboard/groups/_groups.html.haml | 2 - app/views/groups/_activities.html.haml | 2 +- app/views/groups/_archived_projects.html.haml | 3 +- app/views/groups/runners/_settings.html.haml | 14 ++ app/views/ide/_show.html.haml | 2 +- app/views/projects/_activity.html.haml | 2 +- .../projects/merge_requests/show.html.haml | 2 +- app/views/projects/network/show.html.haml | 3 +- .../runners/_specific_runners.html.haml | 2 +- .../shared/nav/_sidebar_submenu.html.haml | 2 +- .../development/issues_full_text_search.yml | 2 +- .../development/source_editor_toolbar.yml | 8 + ...555_add_comment_to_deployment_approvals.rb | 10 + ...t_limit_to_deployment_approvals_comment.rb | 13 + db/schema_migrations/20220303190555 | 1 + db/schema_migrations/20220303191047 | 1 + db/structure.sql | 4 +- doc/api/deployments.md | 6 +- doc/ci/environments/deployment_approvals.md | 2 +- doc/development/snowplow/implementation.md | 5 +- doc/integration/security_partners/index.md | 4 +- doc/integration/twitter.md | 4 +- doc/ssh/index.md | 2 +- doc/topics/autodevops/customize.md | 2 +- doc/topics/autodevops/stages.md | 2 +- doc/topics/gitlab_flow.md | 2 +- doc/user/analytics/code_review_analytics.md | 2 +- doc/user/clusters/applications.md | 4 +- doc/user/clusters/crossplane.md | 2 +- doc/user/clusters/integrations.md | 6 +- .../migrating_from_gma_to_project_template.md | 2 +- doc/user/project/issues/csv_export.md | 10 +- doc/user/project/merge_requests/csv_export.md | 8 +- doc/user/project/web_ide/index.md | 14 +- lib/gitlab/database.rb | 10 + lib/gitlab/gon_helper.rb | 1 + .../group/relation_tree_restorer.rb | 2 +- locale/gitlab.pot | 15 +- qa/Gemfile | 2 + qa/Gemfile.lock | 35 +++ qa/bin/contract | 32 +++ qa/contracts/.gitignore | 2 + qa/contracts/consumer/.node-version | 1 + .../consumer/endpoints/merge_request.js | 42 ++++ .../consumer/fixtures/diffs.fixture.js | 89 +++++++ .../consumer/fixtures/discussions.fixture.js | 85 +++++++ .../consumer/fixtures/metadata.fixture.js | 96 +++++++ qa/contracts/consumer/package.json | 17 ++ qa/contracts/consumer/specs/diffs.spec.js | 35 +++ .../consumer/specs/discussions.spec.js | 35 +++ qa/contracts/consumer/specs/metadata.spec.js | 35 +++ ...est_page-merge_request_diffs_endpoint.json | 228 +++++++++++++++++ ...ge-merge_request_discussions_endpoint.json | 235 ++++++++++++++++++ ..._page-merge_request_metadata_endpoint.json | 222 +++++++++++++++++ qa/contracts/provider/Rakefile | 17 ++ qa/contracts/provider/environments/base.rb | 26 ++ qa/contracts/provider/environments/local.rb | 12 + qa/contracts/provider/spec/diffs_helper.rb | 15 ++ .../provider/spec/discussions_helper.rb | 15 ++ qa/contracts/provider/spec/metadata_helper.rb | 15 ++ spec/features/groups/settings/ci_cd_spec.rb | 18 ++ spec/lib/gitlab/database_spec.rb | 40 +++ spec/models/hooks/web_hook_spec.rb | 12 + spec/policies/project_policy_spec.rb | 96 +++++++ .../web_hooks/log_execution_service_spec.rb | 69 +++++ .../_specific_runners.html.haml_spec.rb | 4 +- 76 files changed, 1725 insertions(+), 67 deletions(-) create mode 100644 app/events/repositories/keep_around_refs_created_event.rb create mode 100644 config/feature_flags/development/source_editor_toolbar.yml create mode 100644 db/migrate/20220303190555_add_comment_to_deployment_approvals.rb create mode 100644 db/migrate/20220303191047_add_text_limit_to_deployment_approvals_comment.rb create mode 100644 db/schema_migrations/20220303190555 create mode 100644 db/schema_migrations/20220303191047 create mode 100755 qa/bin/contract create mode 100644 qa/contracts/.gitignore create mode 100644 qa/contracts/consumer/.node-version create mode 100644 qa/contracts/consumer/endpoints/merge_request.js create mode 100644 qa/contracts/consumer/fixtures/diffs.fixture.js create mode 100644 qa/contracts/consumer/fixtures/discussions.fixture.js create mode 100644 qa/contracts/consumer/fixtures/metadata.fixture.js create mode 100644 qa/contracts/consumer/package.json create mode 100644 qa/contracts/consumer/specs/diffs.spec.js create mode 100644 qa/contracts/consumer/specs/discussions.spec.js create mode 100644 qa/contracts/consumer/specs/metadata.spec.js create mode 100644 qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json create mode 100644 qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json create mode 100644 qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json create mode 100644 qa/contracts/provider/Rakefile create mode 100644 qa/contracts/provider/environments/base.rb create mode 100644 qa/contracts/provider/environments/local.rb create mode 100644 qa/contracts/provider/spec/diffs_helper.rb create mode 100644 qa/contracts/provider/spec/discussions_helper.rb create mode 100644 qa/contracts/provider/spec/metadata_helper.rb 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') }} 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: " "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: " "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: -- [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 `'s GitLab` or `'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(''), + 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( + '

This is a test comment

', + ), + 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": "", + "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": "

This is a test comment

", + "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.'