Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-08 12:20:17 +00:00
parent 60028378dd
commit 6728ed6fe2
76 changed files with 1725 additions and 67 deletions

View File

@ -9,3 +9,4 @@
/sitespeed-result/ /sitespeed-result/
/fixtures/**/*.graphql /fixtures/**/*.graphql
spec/fixtures/**/*.graphql spec/fixtures/**/*.graphql
**/contracts/consumer/

View File

@ -16,7 +16,6 @@ Database/MultipleDatabases:
- lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb - lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
- lib/gitlab/database.rb - lib/gitlab/database.rb
- lib/gitlab/health_checks/db_check.rb - lib/gitlab/health_checks/db_check.rb
- lib/gitlab/import_export/group/relation_tree_restorer.rb
- lib/gitlab/seeder.rb - lib/gitlab/seeder.rb
- spec/db/schema_spec.rb - spec/db/schema_spec.rb
- spec/initializers/database_config_spec.rb - spec/initializers/database_config_spec.rb

View File

@ -431,7 +431,7 @@ export default {
class="js-ide-edit-blob" class="js-ide-edit-blob"
data-qa-selector="edit_in_ide_button" data-qa-selector="edit_in_ide_button"
> >
{{ __('Edit in Web IDE') }} {{ __('Open in Web IDE') }}
</gl-dropdown-item> </gl-dropdown-item>
</template> </template>

View File

@ -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

View File

@ -37,14 +37,14 @@ class WebHook < ApplicationRecord
!temporarily_disabled? && !permanently_disabled? !temporarily_disabled? && !permanently_disabled?
end end
def temporarily_disabled? def temporarily_disabled?(ignore_flag: false)
return false unless web_hooks_disable_failed? return false unless ignore_flag || web_hooks_disable_failed?
disabled_until.present? && disabled_until >= Time.current disabled_until.present? && disabled_until >= Time.current
end end
def permanently_disabled? def permanently_disabled?(ignore_flag: false)
return false unless web_hooks_disable_failed? return false unless ignore_flag || web_hooks_disable_failed?
recent_failures > FAILURE_THRESHOLD recent_failures > FAILURE_THRESHOLD
end end
@ -106,6 +106,13 @@ class WebHook < ApplicationRecord
save(validate: false) save(validate: false)
end 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. # @return [Boolean] Whether or not the WebHook is currently throttled.
def rate_limited? def rate_limited?
return false unless rate_limit return false unless rate_limit

View File

@ -194,6 +194,10 @@ class ProjectPolicy < BasePolicy
condition(:"#{f}_disabled", score: 32) { !access_allowed_to?(f.to_sym) } condition(:"#{f}_disabled", score: 32) { !access_allowed_to?(f.to_sym) }
end 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 # `:read_project` may be prevented in EE, but `:read_project_for_iids` should
# not. # not.
rule { guest | admin }.enable :read_project_for_iids rule { guest | admin }.enable :read_project_for_iids
@ -230,6 +234,8 @@ class ProjectPolicy < BasePolicy
enable :set_emails_disabled enable :set_emails_disabled
enable :set_show_default_award_emojis enable :set_show_default_award_emojis
enable :set_warn_about_potentially_unwanted_characters enable :set_warn_about_potentially_unwanted_characters
enable :register_project_runners
end end
rule { can?(:guest_access) }.policy do rule { can?(:guest_access) }.policy do
@ -453,6 +459,7 @@ class ProjectPolicy < BasePolicy
enable :update_freeze_period enable :update_freeze_period
enable :destroy_freeze_period enable :destroy_freeze_period
enable :admin_feature_flags_client enable :admin_feature_flags_client
enable :register_project_runners
enable :update_runners_registration_token enable :update_runners_registration_token
enable :admin_project_google_cloud enable :admin_project_google_cloud
end end
@ -727,6 +734,10 @@ class ProjectPolicy < BasePolicy
enable :access_security_and_compliance enable :access_security_and_compliance
end end
rule { ~admin & ~project_runner_registration_allowed }.policy do
prevent :register_project_runners
end
private private
def user_is_user? def user_is_user?

View File

@ -23,11 +23,11 @@ module Issues
def header_to_value_hash def header_to_value_hash
{ {
'Title' => 'title',
'Description' => 'description',
'Issue ID' => 'iid', 'Issue ID' => 'iid',
'URL' => -> (issue) { issue_url(issue) }, 'URL' => -> (issue) { issue_url(issue) },
'Title' => 'title',
'State' => -> (issue) { issue.closed? ? 'Closed' : 'Open' }, 'State' => -> (issue) { issue.closed? ? 'Closed' : 'Open' },
'Description' => 'description',
'Author' => 'author_name', 'Author' => 'author_name',
'Author Username' => -> (issue) { issue.author&.username }, 'Author Username' => -> (issue) { issue.author&.username },
'Assignee' => -> (issue) { issue.assignees.map(&:name).join(', ') }, 'Assignee' => -> (issue) { issue.assignees.map(&:name).join(', ') },

View File

@ -13,11 +13,11 @@ module MergeRequests
def header_to_value_hash def header_to_value_hash
{ {
'Title' => 'title',
'Description' => 'description',
'MR IID' => 'iid', 'MR IID' => 'iid',
'URL' => -> (merge_request) { merge_request_url(merge_request) }, 'URL' => -> (merge_request) { merge_request_url(merge_request) },
'Title' => 'title',
'State' => 'state', 'State' => 'state',
'Description' => 'description',
'Source Branch' => 'source_branch', 'Source Branch' => 'source_branch',
'Target Branch' => 'target_branch', 'Target Branch' => 'target_branch',
'Source Project ID' => 'source_project_id', 'Source Project ID' => 'source_project_id',

View File

@ -12,8 +12,9 @@ module WebHooks
def initialize(hook:, log_data:, response_category:) def initialize(hook:, log_data:, response_category:)
@hook = hook @hook = hook
@log_data = log_data @log_data = log_data.transform_keys(&:to_sym)
@response_category = response_category @response_category = response_category
@prev_state = hook.active_state(ignore_flag: true)
end end
def execute def execute
@ -24,7 +25,7 @@ module WebHooks
private private
def log_execution def log_execution
WebHookLog.create!(web_hook: hook, **log_data.transform_keys(&:to_sym)) WebHookLog.create!(web_hook: hook, **log_data)
end end
# Perform this operation within an `Gitlab::ExclusiveLease` lock to make it # Perform this operation within an `Gitlab::ExclusiveLease` lock to make it
@ -41,11 +42,36 @@ module WebHooks
when :failed when :failed
hook.failed! hook.failed!
end end
log_state_change
end end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
raise if raise_lock_error? raise if raise_lock_error?
end 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 def lock_name
"web_hooks:update_hook_failure_state:#{hook.id}" "web_hooks:update_hook_failure_state:#{hook.id}"
end end

View File

@ -6,4 +6,4 @@
.content_list .content_list
.loading .loading
.gl-spinner.gl-spinner-md = gl_loading_icon(size: 'md')

View File

@ -1,4 +1,2 @@
.js-groups-list-holder .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' } } #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

View File

@ -6,4 +6,4 @@
.content_list .content_list
.loading .loading
.gl-spinner.gl-spinner-md = gl_loading_icon(size: 'md')

View File

@ -4,5 +4,4 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } } %ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder .js-groups-list-holder
.loading-container.text-center.prepend-top-20 = gl_loading_icon(size: 'md', css_class: 'gl-mt-6')
.gl-spinner.gl-spinner-md

View File

@ -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' = render 'shared/runners/runner_description'
%hr %hr

View File

@ -9,5 +9,5 @@
#ide.ide-loading{ data: ide_data } #ide.ide-loading{ data: ide_data }
.text-center .text-center
.gl-spinner.gl-spinner-md = gl_loading_icon(size: 'md')
%h2.clgray= _('Loading the GitLab IDE...') %h2.clgray= _('Loading the GitLab IDE...')

View File

@ -11,4 +11,4 @@
.content_list.project-activity{ :"data-href" => activity_project_path(@project) } .content_list.project-activity{ :"data-href" => activity_project_path(@project) }
.loading .loading
.gl-spinner.gl-spinner-md = gl_loading_icon(size: 'md')

View File

@ -83,7 +83,7 @@
.mr-loading-status .mr-loading-status
.loading.hide .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 = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch

View File

@ -16,5 +16,4 @@
- if @commit - 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 } } .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_loading_icon(size: 'md', css_class: 'gl-mt-3')
.gl-spinner.gl-spinner-md

View File

@ -2,7 +2,7 @@
= _('Specific runners') = _('Specific runners')
.bs-callout.help-callout .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.') = _('These runners are specific to this project.')
- if params[:ci_runner_templates] - if params[:ci_runner_templates]
%hr %hr

View File

@ -4,7 +4,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= sidebar_menu.title = sidebar_menu.title
- if sidebar_menu.has_pill? - 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) = number_with_delimiter(sidebar_menu.pill_count)
- if sidebar_menu.has_renderable_items? - if sidebar_menu.has_renderable_items?

View File

@ -1,7 +1,7 @@
--- ---
name: issues_full_text_search name: issues_full_text_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71913 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' milestone: '14.5'
type: development type: development
group: group::project management group: group::project management

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
f8fa8b83da24bf98c97447a2940c8ca801532c80395b6a65c11f83515f811652

View File

@ -0,0 +1 @@
19566152e16a92263dd5dcfd66299e3b9d8b82acd4edb4bba21f6b9b06fc8070

View File

@ -14264,7 +14264,9 @@ CREATE TABLE deployment_approvals (
user_id bigint NOT NULL, user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
updated_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 CREATE SEQUENCE deployment_approvals_id_seq

View File

@ -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. | | `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. | | `deployment_id` | integer | yes | The ID of the deployment. |
| `status` | string | yes | The status of the approval (either `approved` or `rejected`). | | `status` | string | yes | The status of the approval (either `approved` or `rejected`). |
| `comment` | string | no | A comment to go with the approval |
```shell ```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" --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" "web_url": "http://localhost:3000/root"
}, },
"status": "approved", "status": "approved",
"created_at": "2022-02-24T20:22:30.097Z" "created_at": "2022-02-24T20:22:30.097Z",
"comment":"Looks good to me"
} }
``` ```

View File

@ -89,7 +89,7 @@ Using the [Deployments API](../../api/deployments.md#approve-or-reject-a-blocked
Example: Example:
```shell ```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" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments/1/approval"
``` ```

View File

@ -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: The following example shows `data-track-*` attributes assigned to a button:
```haml ```haml
%button.btn{ data: { track: { action: "click_button", label: "template_preview", property: "my-template" } } } %button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
// or
// %button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
``` ```
```html ```html

View File

@ -12,16 +12,16 @@ each security partner:
<!-- vale gitlab.Spelling = NO --> <!-- vale gitlab.Spelling = NO -->
- [Accurics](https://readme.accurics.com/1409/)
- [Anchore](https://docs.anchore.com/current/docs/using/integration/ci_cd/gitlab/) - [Anchore](https://docs.anchore.com/current/docs/using/integration/ci_cd/gitlab/)
- [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed) - [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed)
- [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration) - [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration)
- [Deepfactor](https://docs.deepfactor.io/hc/en-us/articles/1500008981941) - [Deepfactor](https://docs.deepfactor.io/hc/en-us/articles/1500008981941)
- [GrammaTech](https://www.grammatech.com/codesonar-gitlab-integration) - [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) - [JScrambler](https://docs.jscrambler.com/code-integrity/documentation/gitlab-ci-integration)
- [Semgrep](https://semgrep.dev/for/gitlab) - [Semgrep](https://semgrep.dev/for/gitlab)
- [StackHawk](https://docs.stackhawk.com/continuous-integration/gitlab.html) - [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/) - [Venafi](https://marketplace.venafi.com/details/gitlab-ci-cd/)
- [Veracode](https://community.veracode.com/s/knowledgeitem/gitlab-ci-MCEKSYPRWL35BRTGOVI55SK5RI4A) - [Veracode](https://community.veracode.com/s/knowledgeitem/gitlab-ci-MCEKSYPRWL35BRTGOVI55SK5RI4A)
- [WhiteSource](https://www.whitesourcesoftware.com/gitlab/) - [WhiteSource](https://www.whitesourcesoftware.com/gitlab/)

View File

@ -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 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. 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. 1. Fill in the application details.
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or

View File

@ -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 ## 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) 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 has both `git` and `ssh` preinstalled, or install [Git for Windows](https://gitforwindows.org) to
use SSH through Powershell. use SSH through Powershell.

View File

@ -26,7 +26,7 @@ used for the build.
Specify either: 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. - 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 ### Custom buildpacks with Herokuish

View File

@ -112,7 +112,7 @@ Herokuish, with the following caveats:
converted to a Cloud Native Buildpack using Heroku's converted to a Cloud Native Buildpack using Heroku's
[`cnb-shim`](https://github.com/heroku/cnb-shim). [`cnb-shim`](https://github.com/heroku/cnb-shim).
- `BUILDPACK_URL` must be in a format - `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 - 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). commands with `/bin/herokuish procfile exec` is no longer required (nor possible).
Instead, custom commands should be prefixed with `/cnb/lifecycle/launcher` Instead, custom commands should be prefixed with `/cnb/lifecycle/launcher`

View File

@ -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. 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 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. 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. 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. 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). In this flow, it is not common to have a production branch (or Git flow `main` branch).

View File

@ -40,7 +40,7 @@ To view Code Review Analytics:
## Use cases ## 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. and others who want to understand broad code review dynamics, and identify patterns to explain them.
You can use Code Review Analytics to: You can use Code Review Analytics to:

View File

@ -980,7 +980,7 @@ podAnnotations:
The only information to be changed here is the profile name which is `profile-one` The only information to be changed here is the profile name which is `profile-one`
in this example. Refer to the 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. for more information on how AppArmor is integrated in Kubernetes.
#### Using PodSecurityPolicy in your deployments #### Using PodSecurityPolicy in your deployments
@ -1017,7 +1017,7 @@ securityPolicies:
``` ```
This example creates a single policy named `example` with the provided specification, 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 Support for installing the AppArmor managed application is provided by the
GitLab Container Security group. If you run into unknown issues, GitLab Container Security group. If you run into unknown issues,

View File

@ -80,7 +80,7 @@ provided can manage resources in the `database.crossplane.io` API group:
## Configure Crossplane with a cloud provider ## 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. to configure the installed cloud provider stack with a user account.
The Secret, and the Provider resource referencing the Secret, must be The Secret, and the Provider resource referencing the Secret, must be

View File

@ -113,9 +113,9 @@ To use this integration:
`gitlab-managed-apps` namespace. `gitlab-managed-apps` namespace.
1. The `Service` resource must be called `elastic-stack-elasticsearch-master` 1. The `Service` resource must be called `elastic-stack-elasticsearch-master`
and expose the Elasticsearch API on port `9200`. 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) 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.x/exported-fields-log.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.x/add-kubernetes-metadata.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 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 use [this Elastic Stack chart](https://gitlab.com/gitlab-org/charts/elastic-stack) to get up and

View File

@ -120,7 +120,7 @@ you want to manage with the Cluster Management Project.
## Backup and uninstall cert-manager v0.10 ## 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. 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 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. `applications/cert-manager/helmfile.yaml` file.

View File

@ -52,7 +52,7 @@ export, after which the email is prepared.
## Sorting ## Sorting
Exported issues are always sorted by `Issue ID`. Exported issues are always sorted by `Title`.
## Format ## Format
@ -63,11 +63,11 @@ the values:
| Column | Description | | Column | Description |
|------------------------------------------|-----------------------------------------------------------| |------------------------------------------|-----------------------------------------------------------|
| Title | Issue `title` |
| Description | Issue `description` |
| Issue ID | Issue `iid` | | Issue ID | Issue `iid` |
| URL | A link to the issue on GitLab | | URL | A link to the issue on GitLab |
| Title | Issue `title` |
| State | `Open` or `Closed` | | State | `Open` or `Closed` |
| Description | Issue `description` |
| Author | Full name of the issue author | | Author | Full name of the issue author |
| Author Username | Username of the author, with the `@` symbol omitted | | Author Username | Username of the author, with the `@` symbol omitted |
| Assignee | Full name of the issue assignee | | 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) 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 | | [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 ## Limitations
- Export Issues to CSV is not available at the Group's Issues List. - Export Issues to CSV is not available at the Group's Issues List.

View File

@ -18,11 +18,11 @@ The following table shows what attributes will be present in the CSV.
| Column | Description | | Column | Description |
|--------------------|--------------------------------------------------------------| |--------------------|--------------------------------------------------------------|
| Title | Merge request title |
| Description | Merge request description |
| MR ID | MR `iid` | | MR ID | MR `iid` |
| URL | A link to the merge request on GitLab | | URL | A link to the merge request on GitLab |
| Title | Merge request title |
| State | Opened, Closed, Locked, or Merged | | State | Opened, Closed, Locked, or Merged |
| Description | Merge request description |
| Source Branch | Source branch | | Source Branch | Source branch |
| Target Branch | Target branch | | Target Branch | Target branch |
| Source Project ID | ID of the source project | | 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` | | Created At (UTC) | Formatted as `YYYY-MM-DD HH:MM:SS` |
| Updated 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 ## Limitations
- Export merge requests to CSV is not available at the Group's merge request list. - Export merge requests to CSV is not available at the Group's merge request list.

View File

@ -17,16 +17,16 @@ You can also open the Web IDE when viewing a file, from the repository file list
and from merge requests: and from merge requests:
- *When viewing a file, or the repository file list* - - *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. In the upper right corner of the page, select **Open in Web IDE** if it is visible.
1. If **Edit in Web IDE** is not 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 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 **Open 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** to open the editor.
- *When viewing a merge request* - - *When viewing a merge request* -
1. Go to your merge request, and select the **Overview** tab. 1. Go to your merge request, and select the **Overview** tab.
1. Scroll to the widgets section, after the merge request description. 1. Scroll to the widgets section, after the merge request description.
1. Select **Edit in Web IDE** if it is visible. 1. Select **Open in Web IDE** if it is visible.
1. If **Edit in Web IDE** is not visible: 1. If **Open in Web IDE** is not visible:
1. Select the **(angle-down)** next to **Open in Gitpod**. 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** from the list to display it as the editing option.
1. Select **Open in Web IDE** to open the editor. 1. Select **Open in Web IDE** to open the editor.
@ -221,7 +221,7 @@ different branch.
To edit Markdown files in the Web IDE: To edit Markdown files in the Web IDE:
1. Go to your repository, and navigate to the Markdown page you want to edit. 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. 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. 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**. 1. Add a commit message, select the branch you want to commit to, and select **Commit**.

View File

@ -195,6 +195,16 @@ module Gitlab
MAX_TIMESTAMP_VALUE > timestamp ? timestamp : MAX_TIMESTAMP_VALUE.dup MAX_TIMESTAMP_VALUE > timestamp ? timestamp : MAX_TIMESTAMP_VALUE.dup
end 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:) def self.allow_cross_joins_across_databases(url:)
# this method is implemented in: # this method is implemented in:
# spec/support/database/prevent_cross_joins.rb # spec/support/database/prevent_cross_joins.rb

View File

@ -58,6 +58,7 @@ module Gitlab
push_frontend_feature_flag(:new_header_search, default_enabled: :yaml) push_frontend_feature_flag(:new_header_search, default_enabled: :yaml)
push_frontend_feature_flag(:bootstrap_confirmation_modals, 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(:sandboxed_mermaid, default_enabled: :yaml)
push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
end end
# Exposes the state of a feature flag to the frontend code. # Exposes the state of a feature flag to the frontend code.

View File

@ -29,7 +29,7 @@ module Gitlab
end end
def restore def restore
ActiveRecord::Base.uncached do Gitlab::Database.all_uncached do
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
update_params! update_params!

View File

@ -13229,9 +13229,6 @@ msgstr ""
msgid "Edit identity for %{user_name}" msgid "Edit identity for %{user_name}"
msgstr "" msgstr ""
msgid "Edit in Web IDE"
msgstr ""
msgid "Edit in pipeline editor" msgid "Edit in pipeline editor"
msgstr "" msgstr ""
@ -31671,6 +31668,9 @@ msgstr ""
msgid "Runners|Never contacted" msgid "Runners|Never contacted"
msgstr "" msgstr ""
msgid "Runners|New group runners view"
msgstr ""
msgid "Runners|New registration token generated!" msgid "Runners|New registration token generated!"
msgstr "" msgstr ""
@ -31737,6 +31737,9 @@ msgstr ""
msgid "Runners|Revision" msgid "Runners|Revision"
msgstr "" msgstr ""
msgid "Runners|Rocket launch illustration"
msgstr ""
msgid "Runners|Runner" msgid "Runners|Runner"
msgstr "" msgstr ""
@ -31803,6 +31806,12 @@ msgstr ""
msgid "Runners|Tags" msgid "Runners|Tags"
msgstr "" 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?" 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 "" msgstr ""

View File

@ -35,6 +35,8 @@ gem 'confiner', '~> 0.2'
gem 'chemlab', '~> 0.9' gem 'chemlab', '~> 0.9'
gem 'chemlab-library-www-gitlab-com', '~> 0.1' gem 'chemlab-library-www-gitlab-com', '~> 0.1'
gem "pact", "~> 1.12"
gem 'deprecation_toolkit', '~> 1.5.1', require: false gem 'deprecation_toolkit', '~> 1.5.1', require: false
group :development do group :development do

View File

@ -28,6 +28,7 @@ GEM
require_all (>= 2, < 4) require_all (>= 2, < 4)
uuid (>= 2.3, < 3) uuid (>= 2.3, < 3)
ast (2.4.2) ast (2.4.2)
awesome_print (1.9.2)
binding_ninja (0.2.3) binding_ninja (0.2.3)
builder (3.2.4) builder (3.2.4)
byebug (9.1.0) byebug (9.1.0)
@ -91,6 +92,8 @@ GEM
ffi-compiler (1.0.1) ffi-compiler (1.0.1)
ffi (>= 1.0.0) ffi (>= 1.0.0)
rake rake
filelock (1.1.1)
find_a_port (1.0.1)
fog-core (2.1.0) fog-core (2.1.0)
builder builder
excon (~> 0.58) excon (~> 0.58)
@ -173,6 +176,7 @@ GEM
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb-client (1.17.0) influxdb-client (1.17.0)
json (2.6.1)
jwt (2.3.0) jwt (2.3.0)
knapsack (4.0.0) knapsack (4.0.0)
rake rake
@ -205,6 +209,29 @@ GEM
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (3.13.11) oj (3.13.11)
os (1.1.4) 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 (1.19.2)
parallel_tests (2.29.0) parallel_tests (2.29.0)
parallel parallel
@ -228,6 +255,7 @@ GEM
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.6) rake (13.0.6)
randexp (0.1.7)
regexp_parser (2.1.1) regexp_parser (2.1.1)
representable (3.1.1) representable (3.1.1)
declarative (< 0.1.0) declarative (< 0.1.0)
@ -282,12 +310,18 @@ GEM
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)
multi_json (~> 1.10) multi_json (~> 1.10)
slack-notifier (2.4.0) slack-notifier (2.4.0)
sync (0.5.0)
systemu (2.6.5) systemu (2.6.5)
table_print (1.5.7) table_print (1.5.7)
term-ansicolor (1.7.1)
tins (~> 1.0)
terminal-table (3.0.2) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
thor (1.2.1)
thread_safe (0.3.6) thread_safe (0.3.6)
timecop (0.9.1) timecop (0.9.1)
tins (1.31.0)
sync
trailblazer-option (0.1.2) trailblazer-option (0.1.2)
tzinfo (2.0.4) tzinfo (2.0.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
@ -337,6 +371,7 @@ DEPENDENCIES
influxdb-client (~> 1.17) influxdb-client (~> 1.17)
knapsack (~> 4.0) knapsack (~> 4.0)
octokit (~> 4.21) octokit (~> 4.21)
pact (~> 1.12)
parallel (~> 1.19) parallel (~> 1.19)
parallel_tests (~> 2.29) parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1) pry-byebug (~> 3.5.1)

32
qa/bin/contract Executable file
View File

@ -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

2
qa/contracts/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
logs/
consumer/node_modules

View File

@ -0,0 +1 @@
14.17.5

View File

@ -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);
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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"
}
}

View File

@ -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);
});
});
});
},
);

View File

@ -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);
});
});
});
},
);

View File

@ -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);
});
});
});
},
);

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -13,6 +13,24 @@ RSpec.describe 'Group CI/CD settings' do
sign_in(user) sign_in(user)
end 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 describe 'runners registration token' do
let!(:token) { group.runners_token } let!(:token) { group.runners_token }

View File

@ -279,6 +279,46 @@ RSpec.describe Gitlab::Database do
end end
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 describe '.read_only?' do
it 'returns false' do it 'returns false' do
expect(described_class.read_only?).to eq(false) expect(described_class.read_only?).to eq(false)

View File

@ -432,6 +432,12 @@ RSpec.describe WebHook do
expect(hook).not_to be_temporarily_disabled expect(hook).not_to be_temporarily_disabled
end 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
end end
@ -454,6 +460,12 @@ RSpec.describe WebHook do
expect(hook).not_to be_permanently_disabled expect(hook).not_to be_permanently_disabled
end 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
end end

View File

@ -1755,4 +1755,100 @@ RSpec.describe ProjectPolicy do
end end
end 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 end

View File

@ -95,12 +95,37 @@ RSpec.describe WebHooks::LogExecutionService do
it 'resets the failure count' do it 'resets the failure count' do
expect { service.execute }.to change(project_hook, :recent_failures).to(0) expect { service.execute }.to change(project_hook, :recent_failures).to(0)
end 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
end end
context 'when response_category is :failed' do context 'when response_category is :failed' do
let(:response_category) { :failed } let(:response_category) { :failed }
before do
data[:response_status] = '400'
end
it 'increments the failure count' do it 'increments the failure count' do
expect { service.execute }.to change(project_hook, :recent_failures).by(1) expect { service.execute }.to change(project_hook, :recent_failures).by(1)
end end
@ -127,11 +152,36 @@ RSpec.describe WebHooks::LogExecutionService do
expect { service.execute }.not_to change(project_hook, :recent_failures) expect { service.execute }.not_to change(project_hook, :recent_failures)
end end
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 end
context 'when response_category is :error' do context 'when response_category is :error' do
let(:response_category) { :error } let(:response_category) { :error }
before do
data[:response_status] = '500'
end
it 'does not increment the failure count' do it 'does not increment the failure count' do
expect { service.execute }.not_to change(project_hook, :recent_failures) expect { service.execute }.not_to change(project_hook, :recent_failures)
end end
@ -144,6 +194,25 @@ RSpec.describe WebHooks::LogExecutionService do
expect { service.execute }.to change(project_hook, :backoff_count).by(1) expect { service.execute }.to change(project_hook, :backoff_count).by(1)
end 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 context 'when the previous cool-off was near the maximum' do
before do before do
project_hook.update!(disabled_until: 5.minutes.ago, backoff_count: 8) project_hook.update!(disabled_until: 5.minutes.ago, backoff_count: 8)

View File

@ -11,12 +11,14 @@ RSpec.describe 'projects/runners/specific_runners.html.haml' do
@project = project @project = project
@assignable_runners = [] @assignable_runners = []
@project_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') allow(view).to receive(:reset_registration_token_namespace_project_settings_ci_cd_path).and_return('banana_url')
end end
context 'when project runner registration is allowed' do context 'when project runner registration is allowed' do
before do before do
stub_application_setting(valid_runner_registrars: ['project']) stub_application_setting(valid_runner_registrars: ['project'])
allow(view).to receive(:can?).with(user, :register_project_runners, project).and_return(true)
end end
it 'enables the Remove project button for a project' do 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']) stub_application_setting(valid_runner_registrars: ['group'])
end 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 render 'projects/runners/specific_runners', project: project
expect(rendered).to have_content 'Please contact an admin to register runners.' expect(rendered).to have_content 'Please contact an admin to register runners.'