summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-19 03:08:33 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-19 03:08:33 +0000
commit2ea638391497c495798e0bab7c704af112789299 (patch)
tree55dedad2513c731b127e11622134f1791a87cfb0
parent8c80b21468c5c969644c9ea83fec7b43dba1eb3c (diff)
downloadgitlab-ce-2ea638391497c495798e0bab7c704af112789299.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.editorconfig13
-rw-r--r--app/assets/javascripts/import_entities/components/import_status.vue27
-rw-r--r--app/assets/javascripts/import_entities/import_projects/index.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue1
-rw-r--r--app/controllers/import/github_controller.rb4
-rw-r--r--app/graphql/mutations/projects/sync_fork.rb2
-rw-r--r--app/graphql/resolvers/projects/fork_details_resolver.rb1
-rw-r--r--app/helpers/auth_helper.rb7
-rw-r--r--app/models/vulnerability.rb2
-rw-r--r--app/views/admin/applications/_form.html.haml8
-rw-r--r--app/views/admin/applications/index.html.haml4
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/devise/shared/_signup_omniauth_provider_list.haml4
-rw-r--r--app/views/import/_githubish_status.html.haml2
-rw-r--r--app/views/import/github/status.html.haml1
-rw-r--r--app/views/projects/_remove_fork.html.haml2
-rw-r--r--app/views/shared/doorkeeper/applications/_show.html.haml5
-rw-r--r--doc/api/graphql/reference/index.md20
-rw-r--r--doc/development/database/table_partitioning.md45
-rw-r--r--doc/development/documentation/styleguide/word_list.md11
-rw-r--r--doc/user/project/protected_branches.md4
-rw-r--r--lib/api/concerns/packages/debian_package_endpoints.rb25
-rw-r--r--lib/api/debian_project_packages.rb2
-rw-r--r--lib/gitlab/usage_data_counters/counter_events/package_events.yml1
-rw-r--r--lib/gitlab/usage_data_counters/known_events/package_events.yml4
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--qa/qa/ce/strategy.rb3
-rw-r--r--qa/qa/page/admin/applications.rb70
-rw-r--r--qa/qa/page/admin/menu.rb4
-rw-r--r--qa/qa/page/base.rb2
-rw-r--r--qa/qa/page/main/login.rb10
-rw-r--r--qa/qa/page/main/menu.rb18
-rw-r--r--qa/qa/page/profile/menu.rb4
-rw-r--r--qa/qa/page/project/issue/show.rb4
-rw-r--r--qa/qa/page/project/show.rb4
-rw-r--r--qa/qa/page/sub_menus/common.rb4
-rw-r--r--qa/qa/resource/instance_oauth_application.rb49
-rw-r--r--qa/qa/runtime/browser.rb44
-rw-r--r--qa/qa/runtime/env.rb46
-rw-r--r--qa/qa/service/docker_run/base.rb4
-rw-r--r--qa/qa/service/docker_run/gitlab.rb35
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb113
-rw-r--r--qa/qa/specs/spec_helper.rb2
-rw-r--r--qa/qa/tools/test_resources_handler.rb1
-rw-r--r--spec/frontend/import_entities/components/import_status_spec.js70
-rw-r--r--spec/requests/api/debian_group_packages_spec.rb29
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb46
-rw-r--r--spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/fork_details_spec.rb31
51 files changed, 732 insertions, 85 deletions
diff --git a/.editorconfig b/.editorconfig
index 0edad7878b5..0958f2c9591 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -17,5 +17,18 @@ charset = utf-8
[*.{md,markdown,js.snap}]
trim_trailing_whitespace = false
+[doc/**/*.md]
+trim_trailing_whitespace = true
+
[*.rb]
max_line_length = 120
+
+# Don't apply editorconfig rules to vendor/ resources
+[vendor/**]
+charset = unset
+end_of_line = unset
+indent_size = unset
+indent_style = unset
+insert_final_newline = unset
+trim_trailing_whitespace = unset
+max_line_length = unset
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue
index c06258de50c..96d07803545 100644
--- a/app/assets/javascripts/import_entities/components/import_status.vue
+++ b/app/assets/javascripts/import_entities/components/import_status.vue
@@ -1,6 +1,7 @@
<script>
-import { GlAccordion, GlAccordionItem, GlBadge, GlIcon } from '@gitlab/ui';
+import { GlAccordion, GlAccordionItem, GlBadge, GlIcon, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { STATISTIC_ITEMS } from '~/import/constants';
import { STATUSES } from '../constants';
@@ -55,6 +56,13 @@ export default {
GlAccordionItem,
GlBadge,
GlIcon,
+ GlLink,
+ },
+ mixins: [glFeatureFlagMixin()],
+ inject: {
+ detailsPath: {
+ default: undefined,
+ },
},
props: {
status: {
@@ -80,10 +88,13 @@ export default {
return this.stats && this.knownStats.length > 0;
},
+ isIncomplete() {
+ return this.status === STATUSES.FINISHED && this.stats && isIncompleteImport(this.stats);
+ },
+
mappedStatus() {
if (this.status === STATUSES.FINISHED) {
- const isIncomplete = this.stats && isIncompleteImport(this.stats);
- return isIncomplete
+ return this.isIncomplete
? {
icon: 'status-alert',
text: s__('Import|Partially completed'),
@@ -98,6 +109,10 @@ export default {
return STATUS_MAP[this.status];
},
+
+ showDetails() {
+ return Boolean(this.detailsPath) && this.glFeatures.importDetailsPage && this.isIncomplete;
+ },
},
methods: {
@@ -118,6 +133,9 @@ export default {
},
STATISTIC_ITEMS,
+ i18n: {
+ detailsLink: s__('Import|See failures'),
+ },
};
</script>
@@ -130,7 +148,7 @@ export default {
</div>
<gl-accordion v-if="hasStats" :header-level="3">
<gl-accordion-item :title="__('Details')">
- <ul class="gl-p-0 gl-list-style-none gl-font-sm">
+ <ul class="gl-p-0 gl-mb-3 gl-list-style-none gl-font-sm">
<li v-for="key in knownStats" :key="key">
<div class="gl-display-flex gl-w-20 gl-align-items-center">
<gl-icon
@@ -145,6 +163,7 @@ export default {
</div>
</li>
</ul>
+ <gl-link v-if="showDetails" :href="detailsPath">{{ $options.i18n.detailsLink }}</gl-link>
</gl-accordion-item>
</gl-accordion>
</div>
diff --git a/app/assets/javascripts/import_entities/import_projects/index.js b/app/assets/javascripts/import_entities/import_projects/index.js
index 66ffd378426..f898e23b47a 100644
--- a/app/assets/javascripts/import_entities/import_projects/index.js
+++ b/app/assets/javascripts/import_entities/import_projects/index.js
@@ -66,12 +66,16 @@ export default function mountImportProjectsTable({
const store = initStoreFromElement(mountElement);
const props = initPropsFromElement(mountElement);
+ const { detailsPath } = mountElement.dataset;
return new Vue({
el: mountElement,
name: 'ImportProjectsRoot',
store,
apolloProvider,
+ provide: {
+ detailsPath,
+ },
render(createElement) {
// We are using attrs instead of props so root-level component with inheritAttrs
// will be able to pass them down
diff --git a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
index 2f10e068542..dea279890b1 100644
--- a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
+++ b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
@@ -137,6 +137,7 @@ export default {
v-if="showCopyButton"
:text="value"
:title="copyButtonTitle"
+ data-qa-selector="clipboard_button"
@click="handleCopyButtonClick"
/>
</template>
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 6d69187ae11..bd0c0976729 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -11,6 +11,10 @@ class Import::GithubController < Import::BaseController
before_action :provider_auth, only: [:status, :realtime_changes, :create]
before_action :expire_etag_cache, only: [:status, :create]
+ before_action only: [:status] do
+ push_frontend_feature_flag(:import_details_page)
+ end
+
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
rescue_from Octokit::TooManyRequests, with: :provider_rate_limit
rescue_from Gitlab::GithubImport::RateLimitError, with: :rate_limit_threshold_exceeded
diff --git a/app/graphql/mutations/projects/sync_fork.rb b/app/graphql/mutations/projects/sync_fork.rb
index 05332457e8c..13dd0b60d26 100644
--- a/app/graphql/mutations/projects/sync_fork.rb
+++ b/app/graphql/mutations/projects/sync_fork.rb
@@ -25,6 +25,8 @@ module Mutations
return respond(nil, ['Feature flag is disabled']) unless Feature.enabled?(:synchronize_fork,
project.fork_source)
+ return respond(nil, ['Target branch does not exist']) unless project.repository.branch_exists?(target_branch)
+
details_resolver = Resolvers::Projects::ForkDetailsResolver.new(object: project, context: context, field: nil)
details = details_resolver.resolve(ref: target_branch)
diff --git a/app/graphql/resolvers/projects/fork_details_resolver.rb b/app/graphql/resolvers/projects/fork_details_resolver.rb
index 68ac0397787..620ce395915 100644
--- a/app/graphql/resolvers/projects/fork_details_resolver.rb
+++ b/app/graphql/resolvers/projects/fork_details_resolver.rb
@@ -14,7 +14,6 @@ module Resolvers
def resolve(**args)
return unless project.forked?
return unless authorized_fork_source?
- return unless project.repository.branch_exists?(args[:ref])
::Projects::Forks::Details.new(project, args[:ref])
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 00cf8e395bb..b25f80440a9 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -45,10 +45,11 @@ module AuthHelper
provider_has_builtin_icon?(name) || provider_has_custom_icon?(name)
end
- def qa_class_for_provider(provider)
+ def qa_selector_for_provider(provider)
{
- github: 'qa-github-login-button',
- saml: 'qa-saml-login-button'
+ saml: 'saml_login_button',
+ openid_connect: 'oidc_login_button',
+ github: 'qa-github-login-button'
}[provider.to_sym]
end
diff --git a/app/models/vulnerability.rb b/app/models/vulnerability.rb
index 8bb598ee316..650e8942132 100644
--- a/app/models/vulnerability.rb
+++ b/app/models/vulnerability.rb
@@ -7,6 +7,8 @@ class Vulnerability < ApplicationRecord
alias_attribute :vulnerability_id, :id
+ scope :with_projects, -> { includes(:project) }
+
def self.link_reference_pattern
nil
end
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index da5a253041a..a8d5a45041d 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -4,13 +4,13 @@
= content_tag :div, class: 'form-group row' do
.col-12
= f.label :name
- = f.text_field :name, class: 'form-control gl-form-input'
+ = f.text_field :name, class: 'form-control gl-form-input', data: { qa_selector: 'name_field' }
= doorkeeper_errors_for application, :name
= content_tag :div, class: 'form-group row' do
.col-12
= f.label :redirect_uri
- = f.text_area :redirect_uri, class: 'form-control gl-form-input'
+ = f.text_area :redirect_uri, class: 'form-control gl-form-input', data: { qa_selector: 'redirect_uri_field' }
= doorkeeper_errors_for application, :redirect_uri
%span.form-text.text-muted
Use one line per URI
@@ -18,7 +18,7 @@
= content_tag :div, class: 'form-group row' do
.col-12
= f.label :trusted
- = f.gitlab_ui_checkbox_component :trusted, _('Trusted applications are automatically authorized on GitLab OAuth flow. It\'s highly recommended for the security of users that trusted applications have the confidential setting set to true.')
+ = f.gitlab_ui_checkbox_component :trusted, _('Trusted applications are automatically authorized on GitLab OAuth flow. It\'s highly recommended for the security of users that trusted applications have the confidential setting set to true.'), checkbox_options: { data: { qa_selector: 'trusted_checkbox' } }
= content_tag :div, class: 'form-group row' do
.col-12
@@ -31,5 +31,5 @@
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes, f: f
.gl-mt-5
- = f.submit _('Save application'), pajamas_button: true
+ = f.submit _('Save application'), pajamas_button: true, data: { qa_selector: 'save_application_button' }
= link_to _('Cancel'), admin_applications_path, class: "gl-button btn btn-default btn-cancel"
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index 4f7eead71f3..60aa7ae1c56 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -14,12 +14,12 @@
.gl-max-w-full.gl-m-auto
%h1.h4.gl-font-size-h-display= s_('AdminArea|No applications found')
- = render Pajamas::ButtonComponent.new(href: new_admin_application_path, variant: :confirm) do
+ = render Pajamas::ButtonComponent.new(href: new_admin_application_path, variant: :confirm, button_options: { data: { qa_selector: 'new_application_button' } }) do
= s_('New application')
- else
%hr
- = render Pajamas::ButtonComponent.new(href: new_admin_application_path, variant: :confirm) do
+ = render Pajamas::ButtonComponent.new(href: new_admin_application_path, variant: :confirm, button_options: { data: { qa_selector: 'new_application_button' } }) do
= s_('New application')
.table-responsive
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index d89a853b85b..150f61a97e0 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -7,7 +7,7 @@
.gl-display-flex.gl-flex-wrap{ class: restyle_login_page_enabled ? 'gl-justify-content-center' : 'gl-justify-content-between' }
- providers.each do |provider|
- has_icon = provider_has_icon?(provider)
- = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", class: "btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login #{qa_class_for_provider(provider)} #{'gl-w-full' unless restyle_login_page_enabled}", form: { class: restyle_login_page_enabled ? 'gl-mb-3' : 'gl-w-full gl-mb-3' } do
+ = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", data: { qa_selector: "#{qa_selector_for_provider(provider)}" }, class: "btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login #{'gl-w-full' unless restyle_login_page_enabled}", form: { class: restyle_login_page_enabled ? 'gl-mb-3' : 'gl-w-full gl-mb-3' } do
- if has_icon
= provider_image_tag(provider)
%span.gl-button-text
diff --git a/app/views/devise/shared/_signup_omniauth_provider_list.haml b/app/views/devise/shared/_signup_omniauth_provider_list.haml
index a96c8d6358b..99428708b20 100644
--- a/app/views/devise/shared/_signup_omniauth_provider_list.haml
+++ b/app/views/devise/shared/_signup_omniauth_provider_list.haml
@@ -5,7 +5,7 @@
= _("Register with:")
.gl-text-center.gl-w-90p.gl-ml-auto.gl-mr-auto
- providers.each do |provider|
- = link_to omniauth_authorize_path(:user, provider, register_omniauth_params), method: :post, class: "btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login #{qa_class_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
+ = link_to omniauth_authorize_path(:user, provider, register_omniauth_params), method: :post, class: "btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
- if provider_has_icon?(provider)
= provider_image_tag(provider)
%span.gl-button-text
@@ -15,7 +15,7 @@
= _("Create an account using:")
.gl-display-flex.gl-justify-content-between.gl-flex-wrap
- providers.each do |provider|
- = link_to omniauth_authorize_path(:user, provider, register_omniauth_params), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_class_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
+ = link_to omniauth_authorize_path(:user, provider, register_omniauth_params), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
- if provider_has_icon?(provider)
= provider_image_tag(provider)
%span.gl-button-text
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index 4d2186a1352..8545b5fd71d 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -5,6 +5,7 @@
- paginatable = local_assigns.fetch(:paginatable, false)
- default_namespace_path = (local_assigns[:default_namespace] || current_user.namespace).full_path
- cancel_path = local_assigns.fetch(:cancel_path, nil)
+- details_path = local_assigns.fetch(:details_path, nil)
- provider_title = Gitlab::ImportSources.title(local_assigns.fetch(:provider))
- optional_stages = local_assigns.fetch(:optional_stages, [])
@@ -19,6 +20,7 @@
default_target_namespace: default_namespace_path,
import_path: url_for([:import, provider, { format: :json }]),
cancel_path: cancel_path,
+ details_path: details_path,
filterable: filterable.to_s,
paginatable: paginatable.to_s,
optional_stages: optional_stages.to_json }.merge(extra_data) }
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 4a9f8be35c3..45b5a9408be 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -11,4 +11,5 @@
provider: 'github', paginatable: paginatable,
default_namespace: @namespace,
cancel_path: cancel_import_github_path,
+ details_path: details_import_github_path,
optional_stages: Gitlab::GithubImport::Settings.stages_array
diff --git a/app/views/projects/_remove_fork.html.haml b/app/views/projects/_remove_fork.html.haml
index 89ddfa098c8..260c2b2272e 100644
--- a/app/views/projects/_remove_fork.html.haml
+++ b/app/views/projects/_remove_fork.html.haml
@@ -7,5 +7,5 @@
= form_for @project, url: remove_fork_project_path(@project), method: :delete, html: { id: remove_form_id } do |f|
%p
- %strong= _('Once removed, the fork relationship cannot be restored. This project will no longer be able to receive or send merge requests to the source project or other forks.')
+ %strong= _('After it is removed, the fork relationship can only be restored by using the API. This project will no longer be able to receive or send merge requests to the upstream project or other forks.')
.js-confirm-danger{ data: remove_fork_project_confirm_json(@project, remove_form_id) }
diff --git a/app/views/shared/doorkeeper/applications/_show.html.haml b/app/views/shared/doorkeeper/applications/_show.html.haml
index 6bebbe94a55..b9095e2a1a1 100644
--- a/app/views/shared/doorkeeper/applications/_show.html.haml
+++ b/app/views/shared/doorkeeper/applications/_show.html.haml
@@ -8,7 +8,7 @@
%td
.clipboard-group
.input-group
- %input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
+ %input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true, data: { qa_selector: 'application_id_field' } }
.input-group-append
= clipboard_button(target: '#application_id', title: _("Copy ID"), class: "gl-button btn btn-default")
%tr
@@ -46,3 +46,6 @@
= link_to _('Continue'), index_path, class: 'btn btn-confirm btn-md gl-button gl-mr-3'
= link_to _('Edit'), edit_path, class: 'btn btn-default btn-md gl-button'
= render 'shared/doorkeeper/applications/delete_form', path: delete_path
+
+-# Create a hidden field to save the ID of application created
+= hidden_field_tag(:id_of_application, @application.id, data: { qa_selector: 'id_of_application_field' })
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3d3767e450a..170fefa973e 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6525,6 +6525,26 @@ Input type: `VulnerabilityFindingDismissInput`
| <a id="mutationvulnerabilityfindingdismisserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationvulnerabilityfindingdismissfinding"></a>`finding` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | Finding after dismissal. |
+### `Mutation.vulnerabilityIssueLinkCreate`
+
+Input type: `VulnerabilityIssueLinkCreateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationvulnerabilityissuelinkcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationvulnerabilityissuelinkcreateissueid"></a>`issueId` | [`IssueID!`](#issueid) | ID of the issue to link to. |
+| <a id="mutationvulnerabilityissuelinkcreatevulnerabilityids"></a>`vulnerabilityIds` | [`[VulnerabilityID!]!`](#vulnerabilityid) | IDs of vulnerabilities to link to the given issue. Up to 100 can be provided. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationvulnerabilityissuelinkcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationvulnerabilityissuelinkcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationvulnerabilityissuelinkcreateissuelinks"></a>`issueLinks` | [`[VulnerabilityIssueLink!]`](#vulnerabilityissuelink) | Created issue links. |
+
### `Mutation.vulnerabilityResolve`
Input type: `VulnerabilityResolveInput`
diff --git a/doc/development/database/table_partitioning.md b/doc/development/database/table_partitioning.md
index a0a3139daa3..81733b126b6 100644
--- a/doc/development/database/table_partitioning.md
+++ b/doc/development/database/table_partitioning.md
@@ -555,3 +555,48 @@ class Model < ApplicationRecord
self.sequence_name = 'model_id_seq'
end
```
+
+If the partitioning constraint migration takes [more than 10 minutes](../migration_style_guide.md#how-long-a-migration-should-take) to finish,
+it can be made to run asynchronously to avoid running the post-migration during busy hours.
+
+Prepend the following migration `AsyncPrepareTableConstraintsForListPartitioning`
+and use `async: true` option. This change marks the partitioning constraint as `NOT VALID`
+and enqueues a scheduled job to validate the existing data in the table during the weekend.
+
+Then the second post-migration `PrepareTableConstraintsForListPartitioning` only
+marks the partitioning constraint as validated, because the existing data is already
+tested during the previous weekend.
+
+For example:
+
+```ruby
+class AsyncPrepareTableConstraintsForListPartitioning < Gitlab::Database::Migration[2.1]
+ include Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
+
+ disable_ddl_transaction!
+
+ TABLE_NAME = :table_name
+ PARENT_TABLE_NAME = :p_table_name
+ FIRST_PARTITION = 100
+ PARTITION_COLUMN = :partition_id
+
+ def up
+ prepare_constraint_for_list_partitioning(
+ table_name: TABLE_NAME,
+ partitioning_column: PARTITION_COLUMN,
+ parent_table_name: PARENT_TABLE_NAME,
+ initial_partitioning_value: FIRST_PARTITION,
+ async: true
+ )
+ end
+
+ def down
+ revert_preparing_constraint_for_list_partitioning(
+ table_name: TABLE_NAME,
+ partitioning_column: PARTITION_COLUMN,
+ parent_table_name: PARENT_TABLE_NAME,
+ initial_partitioning_value: FIRST_PARTITION
+ )
+ end
+end
+```
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index b644355c54d..eb0576d0c05 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -536,6 +536,17 @@ Filtering is different from [searching](#search).
Do not use **foo** in product documentation. You can use it in our API and contributor documentation, but try to use a clearer and more meaningful example instead.
+## fork
+
+A **fork** is a project that was created from a **upstream project** by using the
+[forking process](../../../topics/git/terminology.md#fork).
+
+The **upstream project** (also known as the **source project**) and the **fork** have a **fork relationship** and are
+**linked**.
+
+If the [**fork relationship** is removed](../../../user/project/repository/forking_workflow.md#unlink-a-fork), the
+**fork** is **unlinked** from the **upstream project**.
+
## future tense
When possible, use present tense instead of future tense. For example, use **after you execute this command, GitLab displays the result** instead of **after you execute this command, GitLab will display the result**. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml))
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index d4115548fca..fa736e79d93 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -100,8 +100,8 @@ To protect a branch for all the projects in a group:
1. On the left sidebar, select **Settings > Repository**.
1. Expand **Protected branches**.
1. In the **Branch** text box, type the branch name or a wildcard.
-1. From the **Allowed to merge** list, select a role, group, or user that can merge into this branch.
-1. From the **Allowed to push** list, select a role, group, or user that can push to this branch.
+1. From the **Allowed to merge** list, select a role that can merge into this branch.
+1. From the **Allowed to push** list, select a role that can push to this branch.
1. Select **Protect**.
The protected branch is added to the list of protected branches.
diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb
index 7969a49909a..db6206a240c 100644
--- a/lib/api/concerns/packages/debian_package_endpoints.rb
+++ b/lib/api/concerns/packages/debian_package_endpoints.rb
@@ -13,6 +13,7 @@ module API
component: ::Packages::Debian::COMPONENT_REGEX,
architecture: ::Packages::Debian::ARCHITECTURE_REGEX
}.freeze
+ LIST_PACKAGE = 'list_package'
included do
feature_category :package_registry
@@ -40,6 +41,8 @@ module API
package_file = distribution_from!(project).package_files.with_file_name(params[:file_name]).last!
+ track_debian_package_event 'pull_package'
+
present_package_file!(package_file)
end
@@ -70,8 +73,22 @@ module API
no_content! # empty component files are not always persisted in DB
end
+ track_debian_package_event LIST_PACKAGE
+
present_carrierwave_file!(component_file.file)
end
+
+ def track_debian_package_event(action)
+ if project_or_group.is_a?(Project)
+ project = project_or_group
+ namespace = project_or_group.namespace
+ else
+ project = nil
+ namespace = project_or_group
+ end
+
+ track_package_event(action, :debian, project: project, namespace: namespace, user: current_user)
+ end
end
rescue_from ArgumentError do |e|
@@ -130,7 +147,9 @@ module API
route_setting :authentication, authenticate_non_public: true
get 'Release' do
- present_carrierwave_file!(distribution_from!(project_or_group).file)
+ distribution = distribution_from!(project_or_group)
+ track_debian_package_event LIST_PACKAGE
+ present_carrierwave_file!(distribution.file)
end
# GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease
@@ -149,7 +168,9 @@ module API
route_setting :authentication, authenticate_non_public: true
get 'InRelease' do
- present_carrierwave_file!(distribution_from!(project_or_group).signed_file)
+ distribution = distribution_from!(project_or_group)
+ track_debian_package_event LIST_PACKAGE
+ present_carrierwave_file!(distribution.signed_file)
end
params do
diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb
index 5324e4158bf..b7350efb49f 100644
--- a/lib/api/debian_project_packages.rb
+++ b/lib/api/debian_project_packages.rb
@@ -109,6 +109,8 @@ module API
::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute
+ track_debian_package_event 'push_package'
+
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })
diff --git a/lib/gitlab/usage_data_counters/counter_events/package_events.yml b/lib/gitlab/usage_data_counters/counter_events/package_events.yml
index f7ddc53f50d..129bf77c7f0 100644
--- a/lib/gitlab/usage_data_counters/counter_events/package_events.yml
+++ b/lib/gitlab/usage_data_counters/counter_events/package_events.yml
@@ -7,6 +7,7 @@
- i_package_conan_push_package
- i_package_debian_delete_package
- i_package_debian_pull_package
+- i_package_debian_push_package
- i_package_delete_package
- i_package_delete_package_by_deploy_token
- i_package_delete_package_by_guest
diff --git a/lib/gitlab/usage_data_counters/known_events/package_events.yml b/lib/gitlab/usage_data_counters/known_events/package_events.yml
index 47cc7f98838..fa99798cde0 100644
--- a/lib/gitlab/usage_data_counters/known_events/package_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/package_events.yml
@@ -7,6 +7,10 @@
aggregation: weekly
- name: i_package_conan_user
aggregation: weekly
+- name: i_package_debian_deploy_token
+ aggregation: weekly
+- name: i_package_debian_user
+ aggregation: weekly
- name: i_package_generic_deploy_token
aggregation: weekly
- name: i_package_generic_user
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 14a32883f2c..270987706e6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3849,6 +3849,9 @@ msgstr ""
msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
msgstr ""
+msgid "After it is removed, the fork relationship can only be restored by using the API. This project will no longer be able to receive or send merge requests to the upstream project or other forks."
+msgstr ""
+
msgid "After the Apple App Store Connect integration is activated, the following protected variables will be created for CI/CD use."
msgstr ""
@@ -22361,6 +22364,9 @@ msgstr ""
msgid "Import|Partially completed"
msgstr ""
+msgid "Import|See failures"
+msgstr ""
+
msgid "Import|The repository could not be imported."
msgstr ""
@@ -30657,9 +30663,6 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr ""
-msgid "Once removed, the fork relationship cannot be restored. This project will no longer be able to receive or send merge requests to the source project or other forks."
-msgstr ""
-
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
diff --git a/qa/Gemfile b/qa/Gemfile
index cd28fd0f0ae..4f138efd06c 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -18,7 +18,7 @@ gem 'faker', '~> 3.2'
gem 'knapsack', '~> 4.0'
gem 'parallel_tests', '~> 4.2'
gem 'rotp', '~> 6.2.2'
-gem 'parallel', '~> 1.22', '>= 1.22.1'
+gem 'parallel', '~> 1.23'
gem 'rainbow', '~> 3.1.1'
gem 'rspec-parameterized', '~> 1.0.0'
gem 'octokit', '~> 6.1.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index b646afd665d..ce37fee0ad4 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -189,7 +189,7 @@ GEM
sawyer (~> 0.9)
oj (3.13.23)
os (1.1.4)
- parallel (1.22.1)
+ parallel (1.23.0)
parallel_tests (4.2.0)
parallel
parser (3.1.3.0)
@@ -323,7 +323,7 @@ DEPENDENCIES
knapsack (~> 4.0)
nokogiri (~> 1.14, >= 1.14.3)
octokit (~> 6.1.1)
- parallel (~> 1.22, >= 1.22.1)
+ parallel (~> 1.23)
parallel_tests (~> 4.2)
pry-byebug (~> 3.10.1)
rainbow (~> 3.1.1)
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index 8143595a18b..2587da17739 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -16,6 +16,9 @@ module QA
QA::Runtime::Env.personal_access_token)
end
+ QA::Runtime::Logger.info("Browser: #{QA::Runtime::Env.browser}")
+ QA::Runtime::Logger.info("Browser version: #{QA::Runtime::Env.browser_version}")
+
# The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests.
QA::Runtime::Logger.info("Performing sanity check for environment!")
diff --git a/qa/qa/page/admin/applications.rb b/qa/qa/page/admin/applications.rb
new file mode 100644
index 00000000000..56880a4b22c
--- /dev/null
+++ b/qa/qa/page/admin/applications.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ class Applications < Page::Base
+ view 'app/views/admin/applications/index.html.haml' do
+ element :new_application_button
+ end
+
+ view 'app/views/admin/applications/_form.html.haml' do
+ element :name_field
+ element :redirect_uri_field
+ element :trusted_checkbox
+ element :save_application_button
+ end
+
+ view 'app/views/shared/tokens/_scopes_form.html.haml' do
+ element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
+ end
+
+ view 'app/views/shared/doorkeeper/applications/_show.html.haml' do
+ element :application_id_field
+ element :id_of_application_field
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue' do
+ element :clipboard_button
+ end
+
+ def click_new_application_button
+ click_element :new_application_button
+ end
+
+ def fill_name(name)
+ fill_element :name_field, name
+ end
+
+ def fill_redirect_uri(redirect_uri)
+ fill_element :redirect_uri_field, redirect_uri
+ end
+
+ def set_trusted_checkbox(value)
+ check_element :trusted_checkbox, value
+ end
+
+ def set_scope(scope)
+ click_element "#{scope}_label".to_sym
+ end
+
+ def save_application
+ click_element :save_application_button
+ end
+
+ def get_secret_id
+ find_element(:clipboard_button)['data-clipboard-text']
+ end
+
+ def get_application_id
+ find_element(:application_id_field).value
+ end
+
+ # Returns the ID of the resource
+ def get_id_of_application
+ find_element(:id_of_application_field, visible: false).value
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index dc6fbb6c081..6ef59e118fb 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -67,6 +67,10 @@ module QA
click_element :admin_overview_groups_link
end
+ def go_to_applications
+ click_element(:sidebar_menu_link, menu_item: 'Applications')
+ end
+
private
def hover_element(element)
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 5d7ca2d60ad..616c12bd900 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -6,7 +6,7 @@ module QA
module Page
class Base
prepend Support::Page::Logging
- prepend Mobile::Page::Base if QA::Runtime::Env.remote_mobile_device_name
+ prepend Mobile::Page::Base if QA::Runtime::Env.mobile_layout?
include Capybara::DSL
include Scenario::Actable
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 2fce357c877..59371dbed39 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -41,6 +41,7 @@ module QA
view 'app/helpers/auth_helper.rb' do
element :saml_login_button
element :github_login_button
+ element :oidc_login_button
end
view 'app/views/layouts/devise.html.haml' do
@@ -188,6 +189,11 @@ module QA
click_element :saml_login_button
end
+ def sign_in_with_oidc
+ set_initial_password_if_present
+ click_element :oidc_login_button
+ end
+
def sign_out_and_sign_in_as(user:)
Menu.perform(&:sign_out_if_signed_in)
has_sign_in_tab?
@@ -232,6 +238,10 @@ module QA
Support::WaitForRequests.wait_for_requests
+ wait_until(sleep_interval: 5, message: '502 - GitLab is taking too much time to respond') do
+ has_no_text?('GitLab is taking too much time to respond')
+ end
+
# For debugging invalid login attempts
has_notice?('Invalid login or password')
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index ad722e85cdd..a46b2057327 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -4,7 +4,9 @@ module QA
module Page
module Main
class Menu < Page::Base
- prepend Mobile::Page::Main::Menu if Runtime::Env.mobile_layout?
+ # We need to check phone_layout? instead of mobile_layout? here
+ # since tablets have the regular top navigation bar
+ prepend Mobile::Page::Main::Menu if Runtime::Env.phone_layout?
if Runtime::Env.super_sidebar_enabled?
prepend SubMenus::CreateNewMenu
@@ -15,8 +17,8 @@ module QA
# Define alternative navbar (super sidebar) which does not yet implement all the same elements
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
element :navbar, required: true # TODO: rename to sidebar once it's default implementation
- element :user_menu, required: !QA::Runtime::Env.mobile_layout?
- element :user_avatar_content, required: !QA::Runtime::Env.mobile_layout?
+ element :user_menu, required: !Runtime::Env.phone_layout?
+ element :user_avatar_content, required: !Runtime::Env.phone_layout?
end
view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do
@@ -27,12 +29,12 @@ module QA
view 'app/views/layouts/header/_default.html.haml' do
element :navbar, required: true
element :canary_badge_link
- element :user_avatar_content, required: !QA::Runtime::Env.mobile_layout?
- element :user_menu, required: !QA::Runtime::Env.mobile_layout?
+ element :user_avatar_content, required: !Runtime::Env.phone_layout?
+ element :user_menu, required: !Runtime::Env.phone_layout?
element :stop_impersonation_link
- element :issues_shortcut_button, required: !QA::Runtime::Env.mobile_layout?
- element :merge_requests_shortcut_button, required: !QA::Runtime::Env.mobile_layout?
- element :todos_shortcut_button, required: !QA::Runtime::Env.mobile_layout?
+ element :issues_shortcut_button, required: !Runtime::Env.phone_layout?
+ element :merge_requests_shortcut_button, required: !Runtime::Env.phone_layout?
+ element :todos_shortcut_button, required: !Runtime::Env.phone_layout?
end
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index 6750a4c847c..f9493dbefcc 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -4,9 +4,7 @@ module QA
module Page
module Profile
class Menu < Page::Base
- # We need to check remote_mobile_device_name instead of mobile_layout? here
- # since tablets have the regular top navigation bar but still close the left nav
- prepend QA::Mobile::Page::SubMenus::Common if QA::Runtime::Env.remote_mobile_device_name
+ prepend QA::Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout?
# TODO: integrate back once super sidebar becomes default
prepend QA::Page::Profile::SuperSidebar::Menu if QA::Runtime::Env.super_sidebar_enabled?
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 2f8ffc634ac..20dce1b3639 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -8,7 +8,9 @@ module QA
include Page::Component::Note
include Page::Component::DesignManagement
include Page::Component::Issuable::Sidebar
- prepend Mobile::Page::Project::Issue::Show if Runtime::Env.mobile_layout?
+ # We need to check phone_layout? instead of mobile_layout? here
+ # since tablets have the regular top navigation bar
+ prepend Mobile::Page::Project::Issue::Show if Runtime::Env.phone_layout?
view 'app/assets/javascripts/issuable/components/related_issuable_item.vue' do
element :remove_related_issue_button
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 8a71544fea9..a76717f4760 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -9,7 +9,9 @@ module QA
include Page::Component::Breadcrumbs
include Page::File::Shared::CommitMessage
include Page::Component::Dropdown
- prepend Mobile::Page::Project::Show if Runtime::Env.mobile_layout?
+ # We need to check phone_layout? instead of mobile_layout? here
+ # since tablets have the regular top navigation bar
+ prepend Mobile::Page::Project::Show if Runtime::Env.phone_layout?
view 'app/assets/javascripts/repository/components/preview/index.vue' do
element :blob_viewer_content
diff --git a/qa/qa/page/sub_menus/common.rb b/qa/qa/page/sub_menus/common.rb
index e04ac3be64f..bbd886706ba 100644
--- a/qa/qa/page/sub_menus/common.rb
+++ b/qa/qa/page/sub_menus/common.rb
@@ -4,9 +4,7 @@ module QA
module Page
module SubMenus
module Common
- # We need to check remote_mobile_device_name instead of mobile_layout? here
- # since tablets have the regular top navigation bar but still close the left nav
- prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.remote_mobile_device_name
+ prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout?
def hover_element(element)
within_sidebar do
diff --git a/qa/qa/resource/instance_oauth_application.rb b/qa/qa/resource/instance_oauth_application.rb
new file mode 100644
index 00000000000..7aa3661717b
--- /dev/null
+++ b/qa/qa/resource/instance_oauth_application.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class InstanceOauthApplication < Base
+ attr_accessor :name, :redirect_uri, :scopes, :trusted
+
+ attributes :id, :application_id, :application_secret
+
+ def initialize
+ @name = "Instance OAuth Application #{SecureRandom.hex(8)}"
+ @redirect_uri = ''
+ @scopes = []
+ @trusted = true
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ '/applications'
+ end
+
+ def api_delete_path
+ "/applications/#{id}"
+ end
+
+ def fabricate!
+ Flow::Login.sign_in_as_admin
+ Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Admin::Menu.perform(&:go_to_applications)
+ Page::Admin::Applications.perform do |app|
+ app.click_new_application_button
+ app.fill_name(name)
+ app.fill_redirect_uri(redirect_uri)
+ app.set_trusted_checkbox(trusted)
+ scopes.each { |scope| app.set_scope(scope) }
+ app.save_application
+ self.application_id = app.get_application_id
+ self.application_secret = app.get_secret_id
+ self.id = app.get_id_of_application
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 945823fb9c0..b67db792419 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -17,6 +17,9 @@ module QA
NotRespondingError = Class.new(RuntimeError)
CAPYBARA_MAX_WAIT_TIME = Env.max_capybara_wait_time
+ DEFAULT_WINDOW_SIZE = '1480,2200'
+ PHONE_VIDEO_SIZE = '500x900'
+ TABLET_VIDEO_SIZE = '800x1100'
def self.blank_page?
['', 'about:blank', 'data:,'].include?(Capybara.current_session.driver.browser.current_url)
@@ -96,11 +99,14 @@ module QA
capabilities['goog:chromeOptions'][:args] << 'disable-dev-shm-usage' if QA::Runtime::Env.disable_dev_shm?
# Set chrome default download path
-
- capabilities['goog:chromeOptions'][:prefs] = {
- 'download.default_directory' => File.expand_path(QA::Runtime::Env.chrome_default_download_path),
- 'download.prompt_for_download' => false
- }
+ # TODO: Set for remote grid as well once Sauce Labs tests are deprecated and Options.chrome is added
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112258
+ unless QA::Runtime::Env.remote_grid
+ capabilities['goog:chromeOptions'][:prefs] = {
+ 'download.default_directory' => File.expand_path(QA::Runtime::Env.chrome_default_download_path),
+ 'download.prompt_for_download' => false
+ }
+ end
# Specify the user-agent to allow challenges to be bypassed
# See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938
@@ -114,7 +120,7 @@ module QA
capabilities['appium:deviceName'] = QA::Runtime::Env.remote_mobile_device_name
capabilities['appium:platformVersion'] = 'latest'
else
- capabilities['goog:chromeOptions'][:args] << 'window-size=1480,2200'
+ capabilities['goog:chromeOptions'][:args] << "window-size=#{DEFAULT_WINDOW_SIZE}"
end
# Slack tries to open an external URL handler
@@ -159,6 +165,9 @@ module QA
when :firefox
capabilities['acceptInsecureCerts'] = true if QA::Runtime::Env.accept_insecure_certs?
+
+ when :edge
+ capabilities['ms:edgeOptions'] = { args: ["--window-size=#{DEFAULT_WINDOW_SIZE}"] }
end
# Use the same profile on QA runs if CHROME_REUSE_PROFILE is true.
@@ -174,10 +183,21 @@ module QA
if QA::Runtime::Env.remote_grid
selenium_options[:browser] = :remote
selenium_options[:url] = QA::Runtime::Env.remote_grid
- capabilities[:browserVersion] = 'latest'
+ capabilities[:browserVersion] = QA::Runtime::Env.browser_version
+ end
+
+ if QA::Runtime::Env.remote_tunnel_id
capabilities['sauce:options'] = { tunnelIdentifier: QA::Runtime::Env.remote_tunnel_id }
end
+ if QA::Runtime::Env.record_video?
+ capabilities['selenoid:options'] = {
+ enableVideo: true,
+ videoScreenSize: video_screen_size,
+ videoName: "#{QA::Runtime::Env.browser}-#{QA::Runtime::Env.browser_version}-#{Time.now}.mp4"
+ }
+ end
+
Capybara::Selenium::Driver.new(
app,
**selenium_options
@@ -233,6 +253,16 @@ module QA
::File.expand_path('../../tmp/qa-profile', __dir__)
end
+ def self.video_screen_size
+ if QA::Runtime::Env.phone_layout?
+ PHONE_VIDEO_SIZE
+ elsif QA::Runtime::Env.tablet_layout?
+ TABLET_VIDEO_SIZE
+ else
+ ''
+ end
+ end
+
class Session
include Capybara::DSL
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 34e392c6263..cde36ba80c4 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -58,6 +58,22 @@ module QA
browser == :chrome && interception_enabled?
end
+ def release
+ ENV['RELEASE']
+ end
+
+ def release_registry_url
+ ENV['RELEASE_REGISTRY_URL']
+ end
+
+ def release_registry_username
+ ENV['RELEASE_REGISTRY_USERNAME']
+ end
+
+ def release_registry_password
+ ENV['RELEASE_REGISTRY_PASSWORD']
+ end
+
def ci_job_url
ENV['CI_JOB_URL']
end
@@ -187,14 +203,38 @@ module QA
ENV['QA_BROWSER'].nil? ? :chrome : ENV['QA_BROWSER'].to_sym
end
+ def browser_version
+ ENV['QA_BROWSER_VERSION'] || 'latest'
+ end
+
def remote_mobile_device_name
- ENV['QA_REMOTE_MOBILE_DEVICE_NAME']
+ ENV['QA_REMOTE_MOBILE_DEVICE_NAME']&.downcase
+ end
+
+ def layout
+ ENV['QA_LAYOUT']&.downcase || ''
+ end
+
+ def tablet_layout?
+ return true if remote_mobile_device_name && !phone_layout?
+
+ layout.include?('tablet')
+ end
+
+ def phone_layout?
+ return true if layout.include?('phone')
+
+ return false unless remote_mobile_device_name
+
+ !(remote_mobile_device_name.include?('ipad') || remote_mobile_device_name.include?('tablet'))
end
def mobile_layout?
- return false if ENV['QA_REMOTE_MOBILE_DEVICE_NAME'].blank?
+ phone_layout? || tablet_layout? || remote_mobile_device_name
+ end
- !(ENV['QA_REMOTE_MOBILE_DEVICE_NAME'].downcase.include?('ipad') || ENV['QA_REMOTE_MOBILE_DEVICE_NAME'].downcase.include?('tablet'))
+ def record_video?
+ enabled?(ENV['QA_RECORD_VIDEO'], default: false)
end
def user_username
diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb
index bf85b640586..ce558849abd 100644
--- a/qa/qa/service/docker_run/base.rb
+++ b/qa/qa/service/docker_run/base.rb
@@ -85,6 +85,10 @@ module QA
shell "docker restart #{@name}"
end
+
+ def health
+ shell("docker inspect --format='{{json .State.Health.Status}}' #{@name}").delete('"')
+ end
end
end
end
diff --git a/qa/qa/service/docker_run/gitlab.rb b/qa/qa/service/docker_run/gitlab.rb
new file mode 100644
index 00000000000..4fbe6603d5b
--- /dev/null
+++ b/qa/qa/service/docker_run/gitlab.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module DockerRun
+ class Gitlab < Base
+ def initialize(name:, omnibus_config: '', image: '')
+ @image = image
+ @name = name
+ @omnibus_configuration = omnibus_config
+ super()
+ end
+
+ def login
+ super(Runtime::Env.release_registry_url,
+ user: Runtime::Env.release_registry_username,
+ password: Runtime::Env.release_registry_password)
+ end
+
+ def register!
+ shell <<~CMD.tr("\n", ' ')
+ docker run -d --rm
+ --network #{network}
+ --hostname #{host_name}
+ --publish 80:80
+ #{RUBY_PLATFORM.include?('arm64') ? '--platform linux/amd64' : ''}
+ --env GITLAB_OMNIBUS_CONFIG="#{@omnibus_configuration}"
+ --name #{@name}
+ #{@image}
+ CMD
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb
new file mode 100644
index 00000000000..268bd6fdbf0
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage', :skip_live_env, requires_admin: 'creates users and instance OAuth application',
+ product_group: :authentication_and_authorization do
+ let!(:user) { Resource::User.fabricate_via_api! }
+ let(:oidc_consumer_name) { 'gitlab-oidc-consumer' }
+ let(:oidc_consumer_host) { "http://#{oidc_consumer_name}.#{Runtime::Env.running_in_ci? ? 'test' : 'bridge'}" }
+ let(:instance_oauth_app) do
+ Resource::InstanceOauthApplication.fabricate! do |application|
+ application.redirect_uri = "#{oidc_consumer_host}/users/auth/openid_connect/callback"
+ application.scopes = %w[openid profile email]
+ end
+ end
+
+ after do
+ instance_oauth_app.remove_via_api!
+ remove_gitlab_service(oidc_consumer_name)
+ end
+
+ # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OIDC idP - OIDC application will be
+ # created here.
+ # GitLab instance stood up in docker with address gitlab-oidc-consumer.test (or gitlab-oidc-consumer.bridge) is
+ # the consumer - The GitLab OIDC Login button will be displayed here.
+ describe 'OIDC' do
+ it(
+ 'creates GitLab OIDC application and uses it to login',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/405137'
+ ) do
+ instance_oauth_app
+
+ Page::Main::Menu.perform(&:sign_out_if_signed_in)
+
+ app_id = instance_oauth_app.application_id
+ app_secret = instance_oauth_app.application_secret
+
+ consumer_gitlab_service = run_gitlab_service(name: oidc_consumer_name, app_id: app_id, app_secret: app_secret)
+
+ wait_for_service(consumer_gitlab_service)
+
+ page.visit oidc_consumer_host
+
+ expect(page.driver.current_url).to include(oidc_consumer_host)
+
+ Page::Main::Login.perform(&:sign_in_with_oidc)
+
+ expect(page.driver.current_url).to include(Runtime::Scenario.gitlab_address)
+
+ Flow::Login.sign_in(as: user)
+
+ expect(page.driver.current_url).to include(oidc_consumer_host)
+
+ Page::Dashboard::Welcome.perform do |welcome|
+ expect(welcome).to have_welcome_title("Welcome to GitLab")
+ end
+ end
+
+ def run_gitlab_service(name:, app_id:, app_secret:)
+ Service::DockerRun::Gitlab.new(
+ image: Runtime::Env.release,
+ name: name,
+ omnibus_config: omnibus_configuration(app_id: app_id, app_secret: app_secret)).tap do |gitlab|
+ gitlab.login
+ gitlab.pull
+ gitlab.register!
+ end
+ end
+
+ def remove_gitlab_service(name)
+ Service::DockerRun::Gitlab.new(name: name).remove!
+ end
+
+ def wait_for_service(service)
+ Support::Waiter.wait_until(max_duration: 900, sleep_interval: 5, raise_on_failure: true) do
+ service.health == "healthy"
+ end
+ end
+
+ def omnibus_configuration(app_id:, app_secret:)
+ <<~OMNIBUS
+ gitlab_rails['initial_root_password']='5iveL\!fe';
+ gitlab_rails['omniauth_enabled'] = true;
+ gitlab_rails['omniauth_allow_single_sign_on'] = true;
+ gitlab_rails['omniauth_block_auto_created_users'] = false;
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'openid_connect',
+ label: 'GitLab OIDC',
+ args: {
+ name: 'openid_connect',
+ scope: ['openid','profile','email'],
+ response_type: 'code',
+ issuer: '#{Runtime::Scenario.gitlab_address}',
+ discovery: false,
+ uid_field: 'preferred_username',
+ send_scope_to_token_endpoint: 'false',
+ client_options: {
+ identifier: '#{app_id}',
+ secret: '#{app_secret}',
+ redirect_uri: '#{oidc_consumer_host}/users/auth/openid_connect/callback',
+ jwks_uri: '#{Runtime::Scenario.gitlab_address}/oauth/discovery/keys',
+ userinfo_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/userinfo',
+ token_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/token',
+ authorization_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/authorize'
+ }
+ }
+ }
+ ];
+ OMNIBUS
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb
index 1bf189ed6ac..aa274a4e101 100644
--- a/qa/qa/specs/spec_helper.rb
+++ b/qa/qa/specs/spec_helper.rb
@@ -33,7 +33,7 @@ RSpec.configure do |config|
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
QA::Runtime::Example.current = example
- visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.remote_mobile_device_name
+ visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.mobile_layout?
# Reset fabrication counters tracked in resource base
Thread.current[:api_fabrication] = 0
diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb
index f30c1902119..7f4b8f78618 100644
--- a/qa/qa/tools/test_resources_handler.rb
+++ b/qa/qa/tools/test_resources_handler.rb
@@ -31,6 +31,7 @@ module QA
QA::Resource::CiVariable
QA::Resource::Repository::Commit
QA::Resource::Design
+ QA::Resource::InstanceOauthApplication
QA::EE::Resource::ComplianceFramework
QA::EE::Resource::GroupIteration
QA::EE::Resource::Settings::Elasticsearch
diff --git a/spec/frontend/import_entities/components/import_status_spec.js b/spec/frontend/import_entities/components/import_status_spec.js
index 7ef60a66796..8e569e3ec85 100644
--- a/spec/frontend/import_entities/components/import_status_spec.js
+++ b/spec/frontend/import_entities/components/import_status_spec.js
@@ -1,4 +1,4 @@
-import { GlAccordionItem, GlBadge, GlIcon } from '@gitlab/ui';
+import { GlAccordionItem, GlBadge, GlIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ImportStatus from '~/import_entities/components/import_status.vue';
import { STATUSES } from '~/import_entities/constants';
@@ -6,12 +6,17 @@ import { STATUSES } from '~/import_entities/constants';
describe('Import entities status component', () => {
let wrapper;
- const createComponent = (propsData) => {
+ const mockStatItems = { label: 100, note: 200 };
+
+ const createComponent = (propsData, { provide } = {}) => {
wrapper = shallowMount(ImportStatus, {
propsData,
+ provide,
});
};
+ const findGlLink = () => wrapper.findComponent(GlLink);
+
describe('success status', () => {
const getStatusText = () => wrapper.findComponent(GlBadge).text();
const getStatusIcon = () => wrapper.findComponent(GlBadge).props('icon');
@@ -24,13 +29,11 @@ describe('Import entities status component', () => {
});
it('displays finished status as complete when all stats items were processed', () => {
- const statItems = { label: 100, note: 200 };
-
createComponent({
status: STATUSES.FINISHED,
stats: {
- fetched: { ...statItems },
- imported: { ...statItems },
+ fetched: { ...mockStatItems },
+ imported: { ...mockStatItems },
},
});
@@ -39,13 +42,11 @@ describe('Import entities status component', () => {
});
it('displays finished status as partial when all stats items were processed', () => {
- const statItems = { label: 100, note: 200 };
-
createComponent({
status: STATUSES.FINISHED,
stats: {
- fetched: { ...statItems },
- imported: { ...statItems, label: 50 },
+ fetched: { ...mockStatItems },
+ imported: { ...mockStatItems, label: 50 },
},
});
@@ -151,4 +152,53 @@ describe('Import entities status component', () => {
expect(getStatusIcon()).toBe('status-success');
});
});
+
+ describe('show details link', () => {
+ const mockDetailsPath = 'details_path';
+ const mockCompleteStats = {
+ fetched: { ...mockStatItems },
+ imported: { ...mockStatItems },
+ };
+ const mockIncompleteStats = {
+ fetched: { ...mockStatItems },
+ imported: { ...mockStatItems, label: 50 },
+ };
+
+ describe.each`
+ detailsPath | importDetailsPage | partialImport | expectLink
+ ${undefined} | ${false} | ${false} | ${false}
+ ${undefined} | ${false} | ${true} | ${false}
+ ${undefined} | ${true} | ${false} | ${false}
+ ${undefined} | ${true} | ${true} | ${false}
+ ${mockDetailsPath} | ${false} | ${false} | ${false}
+ ${mockDetailsPath} | ${false} | ${true} | ${false}
+ ${mockDetailsPath} | ${true} | ${false} | ${false}
+ ${mockDetailsPath} | ${true} | ${true} | ${true}
+ `(
+ 'when detailsPath is $detailsPath, feature flag importDetailsPage is $importDetailsPage, partial import is $partialImport',
+ ({ detailsPath, importDetailsPage, partialImport, expectLink }) => {
+ beforeEach(() => {
+ createComponent(
+ {
+ status: STATUSES.FINISHED,
+ stats: partialImport ? mockIncompleteStats : mockCompleteStats,
+ },
+ {
+ provide: {
+ detailsPath,
+ glFeatures: { importDetailsPage },
+ },
+ },
+ );
+ });
+
+ it(`${expectLink ? 'renders' : 'does not render'} import details link`, () => {
+ expect(findGlLink().exists()).toBe(expectLink);
+ if (expectLink) {
+ expect(findGlLink().attributes('href')).toBe(mockDetailsPath);
+ }
+ });
+ },
+ );
+ });
});
diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb
index 25b99862100..18e5bfd711e 100644
--- a/spec/requests/api/debian_group_packages_spec.rb
+++ b/spec/requests/api/debian_group_packages_spec.rb
@@ -6,28 +6,48 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
include WorkhorseHelpers
include_context 'Debian repository shared context', :group, false do
+ shared_examples 'a Debian package tracking event' do |action|
+ include_context 'Debian repository access', :public, :developer, :basic do
+ let(:snowplow_gitlab_standard_context) do
+ { project: nil, namespace: container, user: user, property: 'i_package_debian_user' }
+ end
+
+ it_behaves_like 'a package tracking event', described_class.name, action
+ end
+ end
+
+ shared_examples 'not a Debian package tracking event' do
+ include_context 'Debian repository access', :public, :developer, :basic do
+ it_behaves_like 'not a package tracking event'
+ end
+ end
+
context 'with invalid parameter' do
let(:url) { "/groups/1/-/packages/debian/dists/with+space/InRelease" }
it_behaves_like 'Debian packages GET request', :bad_request, /^distribution is invalid$/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/InRelease' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/InRelease" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do
@@ -36,12 +56,14 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/Packages" }
it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete Packages file/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages.gz' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages.gz" }
it_behaves_like 'Debian packages read endpoint', 'GET', :not_found, /Format gz is not supported/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do
@@ -51,6 +73,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/source/Sources' do
@@ -59,6 +82,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/Sources" }
it_behaves_like 'Debian packages index endpoint', /^Description: This is an incomplete Sources file$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/source/by-hash/SHA256/:file_sha256' do
@@ -68,6 +92,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages' do
@@ -76,12 +101,14 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/Packages" }
it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete D-I Packages file/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages.gz' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages.gz" }
it_behaves_like 'Debian packages read endpoint', 'GET', :not_found, /Format gz is not supported/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do
@@ -91,6 +118,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
end
describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do
@@ -111,6 +139,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
with_them do
it_behaves_like 'Debian packages read endpoint', 'GET', :success, params[:success_body]
+ it_behaves_like 'a Debian package tracking event', 'pull_package'
context 'for bumping last downloaded at' do
include_context 'Debian repository access', :public, :developer, :basic do
diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb
index e9ad39a08ab..17a413ed059 100644
--- a/spec/requests/api/debian_project_packages_spec.rb
+++ b/spec/requests/api/debian_project_packages_spec.rb
@@ -6,6 +6,22 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
include WorkhorseHelpers
include_context 'Debian repository shared context', :project, false do
+ shared_examples 'a Debian package tracking event' do |action|
+ include_context 'Debian repository access', :public, :developer, :basic do
+ let(:snowplow_gitlab_standard_context) do
+ { project: container, namespace: container.namespace, user: user, property: 'i_package_debian_user' }
+ end
+
+ it_behaves_like 'a package tracking event', described_class.name, action
+ end
+ end
+
+ shared_examples 'not a Debian package tracking event' do
+ include_context 'Debian repository access', :public, :developer, :basic do
+ it_behaves_like 'not a package tracking event'
+ end
+ end
+
shared_examples 'accept GET request on private project with access to package registry for everyone' do
include_context 'Debian repository access', :private, :anonymous, :basic do
before do
@@ -20,12 +36,14 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" }
it_behaves_like 'Debian packages GET request', :bad_request, /^distribution is invalid$/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/
+ it_behaves_like 'not a Debian package tracking event'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -33,6 +51,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -40,6 +59,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" }
it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -49,6 +69,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/Packages" }
it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete Packages file/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -56,6 +77,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages.gz" }
it_behaves_like 'Debian packages read endpoint', 'GET', :not_found, /Format gz is not supported/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do
@@ -65,6 +87,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -74,6 +97,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/Sources" }
it_behaves_like 'Debian packages index endpoint', /^Description: This is an incomplete Sources file$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -84,6 +108,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -93,6 +118,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/Packages" }
it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete D-I Packages file/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -100,6 +126,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages.gz" }
it_behaves_like 'Debian packages read endpoint', 'GET', :not_found, /Format gz is not supported/
+ it_behaves_like 'not a Debian package tracking event'
end
describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do
@@ -109,6 +136,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" }
it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/
+ it_behaves_like 'a Debian package tracking event', 'list_package'
it_behaves_like 'accept GET request on private project with access to package registry for everyone'
end
@@ -130,6 +158,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
with_them do
it_behaves_like 'Debian packages read endpoint', 'GET', :success, params[:success_body]
+ it_behaves_like 'a Debian package tracking event', 'pull_package'
context 'for bumping last downloaded at' do
include_context 'Debian repository access', :public, :developer, :basic do
@@ -146,17 +175,18 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
describe 'PUT projects/:id/packages/debian/:file_name' do
let(:method) { :put }
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}" }
- let(:snowplow_gitlab_standard_context) { { project: container, user: user, namespace: container.namespace } }
context 'with a deb' do
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
+ it_behaves_like 'a Debian package tracking event', 'push_package'
context 'with codename and component' do
let(:extra_params) { { distribution: distribution.codename, component: 'main' } }
it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
+ it_behaves_like 'a Debian package tracking event', 'push_package'
end
context 'with codename and without component' do
@@ -165,6 +195,8 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
include_context 'Debian repository access', :public, :developer, :basic do
it_behaves_like 'Debian packages GET request', :bad_request, /component is missing/
end
+
+ it_behaves_like 'not a Debian package tracking event'
end
end
@@ -173,13 +205,19 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
include_context 'Debian repository access', :public, :developer, :basic do
it_behaves_like "Debian packages upload request", :created, nil
+ end
- context 'with codename and component' do
- let(:extra_params) { { distribution: distribution.codename, component: 'main' } }
+ it_behaves_like 'a Debian package tracking event', 'push_package'
+ context 'with codename and component' do
+ let(:extra_params) { { distribution: distribution.codename, component: 'main' } }
+
+ include_context 'Debian repository access', :public, :developer, :basic do
it_behaves_like "Debian packages upload request", :bad_request,
/^file_name Only debs, udebs and ddebs can be directly added to a distribution$/
end
+
+ it_behaves_like 'not a Debian package tracking event'
end
end
@@ -187,6 +225,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:file_name) { 'sample_1.2.3~alpha2_amd64.changes' }
it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
+ it_behaves_like 'a Debian package tracking event', 'push_package'
end
end
@@ -196,6 +235,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}/authorize" }
it_behaves_like 'Debian packages write endpoint', 'upload authorize', :created, nil
+ it_behaves_like 'not a Debian package tracking event'
end
end
end
diff --git a/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
index 21977b1653e..f3af662c2a0 100644
--- a/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
+++ b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
@@ -110,6 +110,14 @@ RSpec.describe "Sync project fork", feature_category: :source_code_management do
end
end
+ context 'when the specified branch does not exist' do
+ let(:target_branch) { 'non-existent-branch' }
+
+ it 'returns an error' do
+ expect_error_response('Target branch does not exist')
+ end
+ end
+
context 'when the previous execution resulted in a conflict' do
it 'returns an error' do
expect_next_instance_of(::Projects::Forks::Details) do |instance|
diff --git a/spec/requests/api/graphql/project/fork_details_spec.rb b/spec/requests/api/graphql/project/fork_details_spec.rb
index 8cb37549b2a..91a04dc7c50 100644
--- a/spec/requests/api/graphql/project/fork_details_spec.rb
+++ b/spec/requests/api/graphql/project/fork_details_spec.rb
@@ -24,12 +24,23 @@ RSpec.describe 'getting project fork details', feature_category: :source_code_ma
)
end
- it 'returns fork details' do
- post_graphql(query, current_user: current_user)
+ context 'when a ref is specified' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ref, :counts) do
+ 'feature' | { 'ahead' => 1, 'behind' => 29 }
+ 'v1.1.1' | { 'ahead' => 5, 'behind' => 0 }
+ '7b5160f9bb23a3d58a0accdbe89da13b96b1ece9' | { 'ahead' => 9, 'behind' => 0 }
+ 'non-existent-branch' | { 'ahead' => nil, 'behind' => nil }
+ end
- expect(graphql_data['project']['forkDetails']).to eq(
- { 'ahead' => 1, 'behind' => 29 }
- )
+ with_them do
+ it 'returns fork details' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['forkDetails']).to eq(counts)
+ end
+ end
end
context 'when a project is not a fork' do
@@ -52,16 +63,6 @@ RSpec.describe 'getting project fork details', feature_category: :source_code_ma
end
end
- context 'when the specified ref does not exist' do
- let(:ref) { 'non-existent-branch' }
-
- it 'does not return fork details' do
- post_graphql(query, current_user: current_user)
-
- expect(graphql_data['project']['forkDetails']).to be_nil
- end
- end
-
context 'when a user cannot read the code' do
let_it_be(:current_user) { create(:user) }