diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml
index bcaf610bf08..8dd7b4b61d5 100644
--- a/.rubocop_todo/style/percent_literal_delimiters.yml
+++ b/.rubocop_todo/style/percent_literal_delimiters.yml
@@ -3,71 +3,6 @@
Style/PercentLiteralDelimiters:
Exclude:
- 'metrics_server/metrics_server.rb'
- - 'spec/lib/gitlab/alert_management/payload/base_spec.rb'
- - 'spec/lib/gitlab/asset_proxy_spec.rb'
- - 'spec/lib/gitlab/auth/ldap/auth_hash_spec.rb'
- - 'spec/lib/gitlab/auth/ldap/config_spec.rb'
- - 'spec/lib/gitlab/auth/ldap/person_spec.rb'
- - 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
- - 'spec/lib/gitlab/auth/saml/auth_hash_spec.rb'
- - 'spec/lib/gitlab/auth/saml/user_spec.rb'
- - 'spec/lib/gitlab/background_migration/batched_migration_job_spec.rb'
- - 'spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb'
- - 'spec/lib/gitlab/batch_worker_context_spec.rb'
- - 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
- - 'spec/lib/gitlab/cache_spec.rb'
- - 'spec/lib/gitlab/ci/ansi2html_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/bridge_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/commands_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/environment_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/image_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/root_spec.rb'
- - 'spec/lib/gitlab/ci/config/entry/service_spec.rb'
- - 'spec/lib/gitlab/ci/config/extendable/entry_spec.rb'
- - 'spec/lib/gitlab/ci/config/external/file/base_spec.rb'
- - 'spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb'
- - 'spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb'
- - 'spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb'
- - 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb'
- - 'spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb'
- - 'spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb'
- - 'spec/lib/gitlab/ci/reports/test_suite_spec.rb'
- - 'spec/lib/gitlab/ci/status/composite_spec.rb'
- - 'spec/lib/gitlab/ci/status/stage/factory_spec.rb'
- - 'spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb'
- - 'spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb'
- - 'spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb'
- - 'spec/lib/gitlab/ci/variables/collection/item_spec.rb'
- - 'spec/lib/gitlab/ci/yaml_processor/dag_spec.rb'
- - 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
- - 'spec/lib/gitlab/config/entry/factory_spec.rb'
- - 'spec/lib/gitlab/conflict/file_spec.rb'
- - 'spec/lib/gitlab/data_builder/build_spec.rb'
- - 'spec/lib/gitlab/data_builder/pipeline_spec.rb'
- - 'spec/lib/gitlab/data_builder/push_spec.rb'
- - 'spec/lib/gitlab/database/background_migration/batched_job_spec.rb'
- - 'spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb'
- - 'spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb'
- - 'spec/lib/gitlab/database/migration_helpers_spec.rb'
- - 'spec/lib/gitlab/database/postgres_index_spec.rb'
- - 'spec/lib/gitlab/database/reindexing_spec.rb'
- - 'spec/lib/gitlab/database/transaction/observer_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/base_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb'
- - 'spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb'
- - 'spec/lib/gitlab/diff/highlight_spec.rb'
- - 'spec/lib/gitlab/diff/inline_diff_marker_spec.rb'
- 'spec/lib/gitlab/email/handler/service_desk_handler_spec.rb'
- 'spec/lib/gitlab/email/handler_spec.rb'
- 'spec/lib/gitlab/email/receiver_spec.rb'
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 6ab530576fc..5285fa363a5 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -1,5 +1,4 @@
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink } from '@apollo/client/core';
-import { BatchHttpLink } from '@apollo/client/link/batch-http';
import { createUploadLink } from 'apollo-upload-client';
import { persistCache } from 'apollo3-cache-persist';
import ActionCableLink from '~/actioncable_link';
@@ -116,18 +115,14 @@ Object.defineProperty(window, 'pendingApolloRequests', {
function createApolloClient(resolvers = {}, config = {}) {
const {
baseUrl,
- batchMax = 10,
cacheConfig = { typePolicies: {}, possibleTypes: {} },
fetchPolicy = fetchPolicies.CACHE_FIRST,
typeDefs,
httpHeaders = {},
fetchCredentials = 'same-origin',
path = '/api/graphql',
- useGet = false,
} = config;
- const shouldUnbatch = gon.features?.unbatchGraphqlQueries;
-
let ac = null;
let uri = `${gon.relative_url_root || ''}${path}`;
@@ -146,7 +141,6 @@ function createApolloClient(resolvers = {}, config = {}) {
// We set to `same-origin` which is default value in modern browsers.
// See https://github.com/whatwg/fetch/pull/585 for more information.
credentials: fetchCredentials,
- batchMax,
};
/*
@@ -165,14 +159,10 @@ function createApolloClient(resolvers = {}, config = {}) {
return fetch(stripWhitespaceFromQuery(url, uri), options);
};
- const requestLink = ApolloLink.split(
- () => useGet || shouldUnbatch,
- new HttpLink({ ...httpOptions, fetch: fetchIntervention }),
- new BatchHttpLink(httpOptions),
- );
+ const requestLink = new HttpLink({ ...httpOptions, fetch: fetchIntervention });
const uploadsLink = ApolloLink.split(
- (operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
+ (operation) => operation.getContext().hasUpload,
createUploadLink(httpOptions),
);
diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js
index af55a5dc01a..4215cfbf409 100644
--- a/app/assets/javascripts/pages/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,7 +1,6 @@
import $ from 'jquery';
import { setCookie } from '~/lib/utils/common_utils';
import UserCallout from '~/user_callout';
-import { initReportAbuse } from '~/users/profile';
import { initProfileTabs } from '~/profile';
import UserTabs from './user_tabs';
@@ -25,4 +24,3 @@ const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
new UserCallout(); // eslint-disable-line no-new
-initReportAbuse();
diff --git a/app/assets/javascripts/users/profile/components/report_abuse_button.vue b/app/assets/javascripts/users/profile/components/report_abuse_button.vue
deleted file mode 100644
index 0e41a214888..00000000000
--- a/app/assets/javascripts/users/profile/components/report_abuse_button.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/assets/javascripts/users/profile/index.js b/app/assets/javascripts/users/profile/index.js
deleted file mode 100644
index 3ae3cc2de98..00000000000
--- a/app/assets/javascripts/users/profile/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import Vue from 'vue';
-import ReportAbuseButton from './components/report_abuse_button.vue';
-
-export const initReportAbuse = () => {
- const el = document.getElementById('js-report-abuse');
-
- if (!el) return false;
-
- const { reportAbusePath, reportedUserId, reportedFromUrl } = el.dataset;
-
- return new Vue({
- el,
- name: 'ReportAbuseButtonRoot',
- provide: {
- reportAbusePath,
- reportedUserId: reportedUserId ? parseInt(reportedUserId, 10) : null,
- reportedFromUrl,
- },
- render(createElement) {
- return createElement(ReportAbuseButton);
- },
- });
-};
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 7fff31c767f..8db302530ea 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -4,7 +4,6 @@ class SearchController < ApplicationController
include ControllerWithCrossProjectAccessCheck
include SearchHelper
include ProductAnalyticsTracking
- include ProductAnalyticsTracking
include SearchRateLimitable
RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze
@@ -16,6 +15,12 @@ class SearchController < ApplicationController
action: 'executed',
destinations: [:redis_hll, :snowplow]
+ track_event :autocomplete,
+ name: 'i_search_total',
+ label: 'redis_hll_counters.search.search_total_unique_counts_monthly',
+ action: 'autocomplete',
+ destinations: [:redis_hll, :snowplow]
+
def self.search_rate_limited_endpoints
%i[show count autocomplete]
end
@@ -39,10 +44,6 @@ class SearchController < ApplicationController
push_frontend_feature_flag(:search_notes_hide_archived_projects, current_user)
end
- before_action only: :show do
- push_frontend_feature_flag(:search_issues_hide_archived_projects, current_user)
- end
-
before_action only: :show do
push_frontend_feature_flag(:search_merge_requests_hide_archived_projects, current_user)
end
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 331f732bff7..a716740ac08 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -20,6 +20,7 @@ module Ci
filter_by_upgrade_status!
filter_by_runner_type!
filter_by_tag_list!
+ filter_by_creator_id!
sort!
request_tag_list!
@@ -113,6 +114,11 @@ module Ci
end
end
+ def filter_by_creator_id!
+ creator_id = @params[:creator_id]
+ @runners = @runners.with_creator_id(creator_id) if creator_id.present?
+ end
+
def sort!
@runners = @runners.order_by(sort_key)
end
diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb
index 3289f1d0056..efc692f7bab 100644
--- a/app/graphql/resolvers/ci/runners_resolver.rb
+++ b/app/graphql/resolvers/ci/runners_resolver.rb
@@ -41,6 +41,10 @@ module Resolvers
required: false,
description: 'Filter by upgrade status.'
+ argument :creator_id, ::Types::GlobalIDType[::User].as('UserID'),
+ required: false,
+ description: 'Filter runners by creator ID.'
+
def resolve_with_lookahead(**args)
apply_lookahead(
::Ci::RunnersFinder
@@ -68,6 +72,8 @@ module Resolvers
upgrade_status: params[:upgrade_status],
search: params[:search],
sort: params[:sort]&.to_s,
+ creator_id:
+ params[:creator_id] ? ::GitlabSchema.parse_gid(params[:creator_id], expected_type: ::User).model_id : nil,
preload: false # we'll handle preloading ourselves
}.compact
.merge(parent_param)
diff --git a/app/models/activity_pub.rb b/app/models/activity_pub.rb
new file mode 100644
index 00000000000..9131d8be776
--- /dev/null
+++ b/app/models/activity_pub.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ def self.table_name_prefix
+ "activity_pub_"
+ end
+end
diff --git a/app/models/activity_pub/releases_subscription.rb b/app/models/activity_pub/releases_subscription.rb
new file mode 100644
index 00000000000..a6304f1fc35
--- /dev/null
+++ b/app/models/activity_pub/releases_subscription.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ class ReleasesSubscription < ApplicationRecord
+ belongs_to :project, optional: false
+
+ enum :status, [:requested, :accepted], default: :requested
+
+ attribute :payload, Gitlab::Database::Type::JsonPgSafe.new
+
+ validates :payload, json_schema: { filename: 'activity_pub_follow_payload' }, allow_blank: true
+ validates :subscriber_url, presence: true, uniqueness: { case_sensitive: false, scope: :project_id },
+ public_url: true
+ validates :subscriber_inbox_url, uniqueness: { case_sensitive: false, scope: :project_id },
+ public_url: { allow_nil: true }
+ validates :shared_inbox_url, public_url: { allow_nil: true }
+
+ def self.find_by_subscriber_url(subscriber_url)
+ find_by('LOWER(subscriber_url) = ?', subscriber_url.downcase)
+ end
+ end
+end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 91c919dc662..8a9e51ef133 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -123,6 +123,8 @@ module Ci
joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: group_id })
}
+ scope :with_creator_id, -> (value) { where(creator_id: value) }
+
scope :belonging_to_group_or_project_descendants, -> (group_id) {
group_ids = Ci::NamespaceMirror.by_group_and_descendants(group_id).select(:namespace_id)
project_ids = Ci::ProjectMirror.by_namespace_id(group_ids).select(:project_id)
diff --git a/app/models/note.rb b/app/models/note.rb
index eae7a40fb4e..6f4a56dd3cc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -383,7 +383,11 @@ class Note < ApplicationRecord
end
def for_project_noteable?
- !(for_personal_snippet? || for_abuse_report?)
+ !(for_personal_snippet? || for_abuse_report? || group_level_issue?)
+ end
+
+ def group_level_issue?
+ (for_issue? || for_work_item?) && noteable&.project_id.blank?
end
def for_design?
diff --git a/app/services/import/validate_remote_git_endpoint_service.rb b/app/services/import/validate_remote_git_endpoint_service.rb
index 2177238fddf..a994072c4aa 100644
--- a/app/services/import/validate_remote_git_endpoint_service.rb
+++ b/app/services/import/validate_remote_git_endpoint_service.rb
@@ -13,8 +13,6 @@ module Import
GIT_PROTOCOL_PKT_LEN = 4
GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length
EXPECTED_CONTENT_TYPE = "application/x-#{GIT_SERVICE_NAME}-advertisement"
- INVALID_BODY_MESSAGE = 'Not a git repository: Invalid response body'
- INVALID_CONTENT_TYPE_MESSAGE = 'Not a git repository: Invalid content-type'
def initialize(params)
@params = params
@@ -32,35 +30,32 @@ module Import
uri.fragment = nil
url = Gitlab::Utils.append_path(uri.to_s, "/info/refs?service=#{GIT_SERVICE_NAME}")
- response, response_body = http_get_and_extract_first_chunks(url)
+ response_body = ''
+ result = nil
+ Gitlab::HTTP.try_get(url, stream_body: true, follow_redirects: false, basic_auth: auth) do |fragment|
+ response_body += fragment
+ next if response_body.length < GIT_MINIMUM_RESPONSE_LENGTH
- validate(uri, response, response_body)
- rescue *Gitlab::HTTP::HTTP_ERRORS => err
- error_result("HTTP #{err.class.name.underscore} error: #{err.message}")
- rescue StandardError => err
- ServiceResponse.error(
- message: "Internal #{err.class.name.underscore} error: #{err.message}",
- reason: 500
- )
+ result = if status_code_is_valid(fragment) && content_type_is_valid(fragment) && response_body_is_valid(response_body)
+ :success
+ else
+ :error
+ end
+
+ # We are interested only in the first chunks of the response
+ # So we're using stream_body: true and breaking when receive enough body
+ break
+ end
+
+ if result == :success
+ ServiceResponse.success
+ else
+ ServiceResponse.error(message: "#{uri} is not a valid HTTP Git repository")
+ end
end
private
- def http_get_and_extract_first_chunks(url)
- # We are interested only in the first chunks of the response
- # So we're using stream_body: true and breaking when receive enough body
- response = nil
- response_body = ''
-
- Gitlab::HTTP.get(url, stream_body: true, follow_redirects: false, basic_auth: auth) do |response_chunk|
- response = response_chunk
- response_body += response_chunk
- break if GIT_MINIMUM_RESPONSE_LENGTH <= response_body.length
- end
-
- [response, response_body]
- end
-
def auth
unless @params[:user].to_s.blank?
{
@@ -70,38 +65,16 @@ module Import
end
end
- def validate(uri, response, response_body)
- return status_code_error(uri, response) unless status_code_is_valid?(response)
- return error_result(INVALID_CONTENT_TYPE_MESSAGE) unless content_type_is_valid?(response)
- return error_result(INVALID_BODY_MESSAGE) unless response_body_is_valid?(response_body)
-
- ServiceResponse.success
+ def status_code_is_valid(fragment)
+ fragment.http_response.code == '200'
end
- def status_code_error(uri, response)
- http_code = response.http_response.code.to_i
- message = response.http_response.message || Rack::Utils::HTTP_STATUS_CODES[http_code]
-
- error_result(
- "#{uri} endpoint error: #{http_code}#{message.presence&.prepend(' ')}",
- http_code
- )
+ def content_type_is_valid(fragment)
+ fragment.http_response['content-type'] == EXPECTED_CONTENT_TYPE
end
- def error_result(message, reason = nil)
- ServiceResponse.error(message: message, reason: reason)
- end
-
- def status_code_is_valid?(response)
- response.http_response.code == '200'
- end
-
- def content_type_is_valid?(response)
- response.http_response['content-type'] == EXPECTED_CONTENT_TYPE
- end
-
- def response_body_is_valid?(response_body)
- response_body.length <= GIT_MINIMUM_RESPONSE_LENGTH && response_body.match?(GIT_BODY_MESSAGE_REGEXP)
+ def response_body_is_valid(response_body)
+ response_body.match?(GIT_BODY_MESSAGE_REGEXP)
end
end
end
diff --git a/app/validators/json_schemas/activity_pub_follow_payload.json b/app/validators/json_schemas/activity_pub_follow_payload.json
new file mode 100644
index 00000000000..1f453ce840f
--- /dev/null
+++ b/app/validators/json_schemas/activity_pub_follow_payload.json
@@ -0,0 +1,53 @@
+{
+ "description": "ActivityPub Follow activity payload",
+ "type": "object",
+ "required": [
+ "@context",
+ "id",
+ "type",
+ "actor",
+ "object"
+ ],
+ "properties": {
+ "@context": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array"
+ }
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "actor": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "id": {
+ "type": "string"
+ },
+ "inbox": {
+ "type": "string"
+ },
+ "additionalProperties": true
+ }
+ ]
+ },
+ "object": {
+ "type": "string"
+ },
+ "additionalProperties": true
+ }
+}
diff --git a/app/views/users/_cover_controls.html.haml b/app/views/users/_cover_controls.html.haml
deleted file mode 100644
index 899a08c8a17..00000000000
--- a/app/views/users/_cover_controls.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-.cover-controls.gl-display-flex.gl-gap-3.gl-pb-4
- = yield
diff --git a/app/views/users/_profile_basic_info.html.haml b/app/views/users/_profile_basic_info.html.haml
index 6de9e80008e..7dd131dbe2c 100644
--- a/app/views/users/_profile_basic_info.html.haml
+++ b/app/views/users/_profile_basic_info.html.haml
@@ -2,9 +2,5 @@
= render 'middle_dot_divider', stacking: true do
@#{@user.username}
- if can?(current_user, :read_user_profile, @user)
- - unless Feature.enabled?(:user_profile_overflow_menu_vue)
- = render 'middle_dot_divider', stacking: true do
- = s_('UserProfile|User ID: %{id}') % { id: @user.id }
- = clipboard_button(title: s_('UserProfile|Copy user ID'), text: @user.id)
= render 'middle_dot_divider', stacking: true do
= s_('Member since %{date}') % { date: l(@user.created_at.to_date, format: :long) }
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0881c5bba54..034e7f18bbe 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -17,32 +17,16 @@
.user-profile
.cover-block.user-cover-block.gl-border-t.gl-border-b.gl-mt-n1
%div{ class: container_class }
- - if Feature.enabled?(:user_profile_overflow_menu_vue)
- .cover-controls.gl-display-flex.gl-gap-3.gl-pb-4
- = render 'users/follow_user'
- -# The following edit button is mutually exclusive to the follow user button, they won't be shown together
- - if @user == current_user
- = render Pajamas::ButtonComponent.new(href: profile_path,
- button_options: { class: 'gl-flex-grow-1', title: s_('UserProfile|Edit profile') }) do
- = s_("UserProfile|Edit profile")
- = render 'users/view_gpg_keys'
- = render 'users/view_user_in_admin_area'
- .js-user-profile-actions{ data: user_profile_actions_data(@user) }
- - else
- = render layout: 'users/cover_controls' do
- - if @user == current_user
- = render Pajamas::ButtonComponent.new(href: profile_path,
- icon: 'pencil',
- button_options: { class: 'gl-flex-grow-1 has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }})
- - elsif current_user
- #js-report-abuse{ data: { report_abuse_path: add_category_abuse_reports_path, reported_user_id: @user.id, reported_from_url: user_url(@user) } }
- = render 'users/view_gpg_keys'
- - if can?(current_user, :read_user_profile, @user)
- = render Pajamas::ButtonComponent.new(href: user_path(@user, rss_url_options),
- icon: 'rss',
- button_options: { class: 'gl-flex-grow-1 has-tooltip', title: s_('UserProfile|Subscribe'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }})
- = render 'users/view_user_in_admin_area'
- = render 'users/follow_user'
+ .cover-controls.gl-display-flex.gl-gap-3.gl-pb-4
+ = render 'users/follow_user'
+ -# The following edit button is mutually exclusive to the follow user button, they won't be shown together
+ - if @user == current_user
+ = render Pajamas::ButtonComponent.new(href: profile_path,
+ button_options: { class: 'gl-flex-grow-1', title: s_('UserProfile|Edit profile') }) do
+ = s_("UserProfile|Edit profile")
+ = render 'users/view_gpg_keys'
+ = render 'users/view_user_in_admin_area'
+ .js-user-profile-actions{ data: user_profile_actions_data(@user) }
.profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?), ('gl-mb-4!' if show_super_sidebar?)] }
.gl-display-inline-block.gl-mx-8.gl-vertical-align-top
diff --git a/config/feature_flags/development/search_issues_hide_archived_projects.yml b/config/feature_flags/development/search_issues_hide_archived_projects.yml
deleted file mode 100644
index 68a6d058e81..00000000000
--- a/config/feature_flags/development/search_issues_hide_archived_projects.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: search_issues_hide_archived_projects
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124846
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416483
-milestone: '16.2'
-type: development
-group: group::global search
-default_enabled: false
diff --git a/config/feature_flags/development/unbatch_graphql_queries.yml b/config/feature_flags/development/unbatch_graphql_queries.yml
deleted file mode 100644
index 8a78a46c109..00000000000
--- a/config/feature_flags/development/unbatch_graphql_queries.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: unbatch_graphql_queries
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117407
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/406765
-milestone: '16.0'
-type: development
-group: group::project management
-default_enabled: true
diff --git a/config/feature_flags/development/user_profile_overflow_menu_vue.yml b/config/feature_flags/development/user_profile_overflow_menu_vue.yml
deleted file mode 100644
index 42a792414cf..00000000000
--- a/config/feature_flags/development/user_profile_overflow_menu_vue.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: user_profile_overflow_menu_vue
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414773
-milestone: '16.1'
-type: development
-group: group::tenant scale
-default_enabled: false
diff --git a/config/initializers/database_query_analyzers.rb b/config/initializers/database_query_analyzers.rb
index 5c2f3caf89e..9facd822e5c 100644
--- a/config/initializers/database_query_analyzers.rb
+++ b/config/initializers/database_query_analyzers.rb
@@ -9,7 +9,10 @@ Gitlab::Database::QueryAnalyzer.instance.tap do |query_analyzer|
analyzers.append(::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification)
analyzers.append(::Gitlab::Database::QueryAnalyzers::Ci::PartitioningRoutingAnalyzer)
- analyzers.append(::Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection) if Gitlab.dev_or_test_env?
+ if Gitlab.dev_or_test_env?
+ analyzers.append(::Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection)
+ analyzers.append(::Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch)
+ end
end
end
diff --git a/db/docs/activity_pub_releases_subscriptions.yml b/db/docs/activity_pub_releases_subscriptions.yml
new file mode 100644
index 00000000000..8a27a51f9f3
--- /dev/null
+++ b/db/docs/activity_pub_releases_subscriptions.yml
@@ -0,0 +1,10 @@
+---
+table_name: activity_pub_releases_subscriptions
+classes:
+- ActivityPub::ReleasesSubscription
+feature_categories:
+- release_orchestration
+description: Stores subscriptions from external users through ActivityPub for project
+ releases
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132889
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230529182720_recreate_billable_index.rb b/db/migrate/20230529182720_recreate_billable_index.rb
index 5e56dd7005a..a983dc5f295 100644
--- a/db/migrate/20230529182720_recreate_billable_index.rb
+++ b/db/migrate/20230529182720_recreate_billable_index.rb
@@ -8,8 +8,10 @@ class RecreateBillableIndex < Gitlab::Database::Migration[2.1]
def up
remove_concurrent_index_by_name :users, INDEX_NAME
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, :id, name: INDEX_NAME,
where: "state = 'active' AND (user_type IN (0, 6, 4, 13)) AND (user_type IN (0, 4, 5))"
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/migrate/20230529184716_recreated_activity_index.rb b/db/migrate/20230529184716_recreated_activity_index.rb
index 2b949d39de1..c5c76b8ec14 100644
--- a/db/migrate/20230529184716_recreated_activity_index.rb
+++ b/db/migrate/20230529184716_recreated_activity_index.rb
@@ -8,9 +8,11 @@ class RecreatedActivityIndex < Gitlab::Database::Migration[2.1]
def up
remove_concurrent_index_by_name :users, INDEX_NAME
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, [:id, :last_activity_on],
name: INDEX_NAME,
where: "state = 'active' AND user_type IN (0, 4)"
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/migrate/20230605043258_add_unconfirmed_created_at_index_to_users.rb b/db/migrate/20230605043258_add_unconfirmed_created_at_index_to_users.rb
index 65bd7a1266b..bd3a7006972 100644
--- a/db/migrate/20230605043258_add_unconfirmed_created_at_index_to_users.rb
+++ b/db/migrate/20230605043258_add_unconfirmed_created_at_index_to_users.rb
@@ -6,9 +6,11 @@ class AddUnconfirmedCreatedAtIndexToUsers < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_users_on_unconfirmed_and_created_at_for_active_humans'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, [:created_at, :id],
name: INDEX_NAME,
where: "confirmed_at IS NULL AND state = 'active' AND user_type IN (0)"
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/migrate/20231017095738_create_activity_pub_releases_subscriptions.rb b/db/migrate/20231017095738_create_activity_pub_releases_subscriptions.rb
new file mode 100644
index 00000000000..19693c29a33
--- /dev/null
+++ b/db/migrate/20231017095738_create_activity_pub_releases_subscriptions.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CreateActivityPubReleasesSubscriptions < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ def up
+ create_table :activity_pub_releases_subscriptions do |t|
+ t.references :project, index: false, foreign_key: { on_delete: :cascade }, null: false
+ t.timestamps_with_timezone null: false
+ t.integer :status, null: false, limit: 2, default: 1
+ t.text :shared_inbox_url, limit: 1024
+ t.text :subscriber_inbox_url, limit: 1024
+ t.text :subscriber_url, limit: 1024, null: false
+ t.jsonb :payload, null: true
+ t.index 'project_id, LOWER(subscriber_url)', name: :index_activity_pub_releases_sub_on_project_id_sub_url,
+ unique: true
+ t.index 'project_id, LOWER(subscriber_inbox_url)',
+ name: :index_activity_pub_releases_sub_on_project_id_inbox_url, unique: true
+ end
+ end
+
+ def down
+ drop_table :activity_pub_releases_subscriptions
+ end
+end
diff --git a/db/post_migrate/20220617123022_add_unique_index_on_projects_on_runners_token.rb b/db/post_migrate/20220617123022_add_unique_index_on_projects_on_runners_token.rb
index 1e0409b16ea..abd730685d7 100644
--- a/db/post_migrate/20220617123022_add_unique_index_on_projects_on_runners_token.rb
+++ b/db/post_migrate/20220617123022_add_unique_index_on_projects_on_runners_token.rb
@@ -6,10 +6,12 @@ class AddUniqueIndexOnProjectsOnRunnersToken < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_uniq_projects_on_runners_token'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects,
:runners_token,
name: INDEX_NAME,
unique: true
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20220617123034_add_unique_index_on_projects_on_runners_token_encrypted.rb b/db/post_migrate/20220617123034_add_unique_index_on_projects_on_runners_token_encrypted.rb
index b9ba570606e..51b630397dc 100644
--- a/db/post_migrate/20220617123034_add_unique_index_on_projects_on_runners_token_encrypted.rb
+++ b/db/post_migrate/20220617123034_add_unique_index_on_projects_on_runners_token_encrypted.rb
@@ -6,10 +6,12 @@ class AddUniqueIndexOnProjectsOnRunnersTokenEncrypted < Gitlab::Database::Migrat
INDEX_NAME = 'index_uniq_projects_on_runners_token_encrypted'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects,
:runners_token_encrypted,
name: INDEX_NAME,
unique: true
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20220920135356_tiebreak_user_type_index.rb b/db/post_migrate/20220920135356_tiebreak_user_type_index.rb
index 778a957086f..489196c8eab 100644
--- a/db/post_migrate/20220920135356_tiebreak_user_type_index.rb
+++ b/db/post_migrate/20220920135356_tiebreak_user_type_index.rb
@@ -7,7 +7,9 @@ class TiebreakUserTypeIndex < Gitlab::Database::Migration[2.0]
OLD_INDEX_NAME = 'index_users_on_user_type'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, [:user_type, :id], name: NEW_INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
remove_concurrent_index_by_name :users, OLD_INDEX_NAME
end
diff --git a/db/post_migrate/20221018232820_add_temp_index_for_user_details_fields.rb b/db/post_migrate/20221018232820_add_temp_index_for_user_details_fields.rb
index b46b316981d..1cb93886ca3 100644
--- a/db/post_migrate/20221018232820_add_temp_index_for_user_details_fields.rb
+++ b/db/post_migrate/20221018232820_add_temp_index_for_user_details_fields.rb
@@ -6,6 +6,7 @@ class AddTempIndexForUserDetailsFields < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, :id, name: INDEX_NAME, where: <<~QUERY
(COALESCE(linkedin, '') IS DISTINCT FROM '')
OR (COALESCE(twitter, '') IS DISTINCT FROM '')
@@ -14,6 +15,7 @@ class AddTempIndexForUserDetailsFields < Gitlab::Database::Migration[2.0]
OR (COALESCE(location, '') IS DISTINCT FROM '')
OR (COALESCE(organization, '') IS DISTINCT FROM '')
QUERY
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20221221150123_update_billable_users_index.rb b/db/post_migrate/20221221150123_update_billable_users_index.rb
index d2f55e06b0b..d77669f6a69 100644
--- a/db/post_migrate/20221221150123_update_billable_users_index.rb
+++ b/db/post_migrate/20221221150123_update_billable_users_index.rb
@@ -16,7 +16,9 @@ class UpdateBillableUsersIndex < Gitlab::Database::Migration[2.1]
QUERY
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index(:users, :id, where: NEW_INDEX_CONDITION, name: NEW_INDEX)
+ # rubocop:enable Migration/PreventIndexCreation
remove_concurrent_index_by_name(:users, OLD_INDEX)
end
diff --git a/db/post_migrate/20230131184319_update_billable_users_index_for_service_accounts.rb b/db/post_migrate/20230131184319_update_billable_users_index_for_service_accounts.rb
index e86a2476156..842c7295fcb 100644
--- a/db/post_migrate/20230131184319_update_billable_users_index_for_service_accounts.rb
+++ b/db/post_migrate/20230131184319_update_billable_users_index_for_service_accounts.rb
@@ -16,7 +16,9 @@ class UpdateBillableUsersIndexForServiceAccounts < Gitlab::Database::Migration[2
QUERY
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index(:users, :id, where: NEW_INDEX_CONDITION, name: NEW_INDEX)
+ # rubocop:enable Migration/PreventIndexCreation
remove_concurrent_index_by_name(:users, OLD_INDEX)
end
diff --git a/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb b/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb
index 8f9e193f0eb..d4f48c1c977 100644
--- a/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb
+++ b/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb
@@ -6,6 +6,7 @@ class AddUserTypeMigrationIndexes < Gitlab::Database::Migration[2.1]
BILLABLE_INDEX = 'index_users_for_active_billable_users_migration'
LAST_ACTIVITY_INDEX = 'i_users_on_last_activity_for_active_human_service_migration'
+ # rubocop:disable Migration/PreventIndexCreation
def up
# Temporary indexes to migrate human user_type. See https://gitlab.com/gitlab-org/gitlab/-/issues/386474
add_concurrent_index :users, :id, name: BILLABLE_INDEX,
@@ -14,6 +15,7 @@ class AddUserTypeMigrationIndexes < Gitlab::Database::Migration[2.1]
add_concurrent_index :users, [:id, :last_activity_on], name: LAST_ACTIVITY_INDEX,
where: "((state)::text = 'active'::text) AND ((user_type IS NULL OR user_type = 0) OR (user_type = 4))"
end
+ # rubocop:enable Migration/PreventIndexCreation
def down
remove_concurrent_index_by_name :users, BILLABLE_INDEX
diff --git a/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb b/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb
index 539ce99a319..147409bf5f0 100644
--- a/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb
+++ b/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb
@@ -8,9 +8,11 @@ class RecreateUserTypeMigrationIndexes < Gitlab::Database::Migration[2.1]
def up
# Temporary index to migrate human user_type. See https://gitlab.com/gitlab-org/gitlab/-/issues/386474
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, :id, name: BILLABLE_INDEX,
where: "state = 'active' AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[0, 6, 4, 13]))) " \
"AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[0, 4, 5])))"
+ # rubocop:enable Migration/PreventIndexCreation
remove_concurrent_index_by_name :users, INCORRECT_BILLABLE_INDEX
end
diff --git a/db/post_migrate/20230619005223_change_unconfirmed_created_at_index_on_users.rb b/db/post_migrate/20230619005223_change_unconfirmed_created_at_index_on_users.rb
index 5b9b4e36512..e299ce394a3 100644
--- a/db/post_migrate/20230619005223_change_unconfirmed_created_at_index_on_users.rb
+++ b/db/post_migrate/20230619005223_change_unconfirmed_created_at_index_on_users.rb
@@ -7,9 +7,11 @@ class ChangeUnconfirmedCreatedAtIndexOnUsers < Gitlab::Database::Migration[2.1]
NEW_INDEX_NAME = 'index_users_on_unconfirmed_created_at_active_type_sign_in_count'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, [:created_at, :id],
name: NEW_INDEX_NAME,
where: "confirmed_at IS NULL AND state = 'active' AND user_type IN (0) AND sign_in_count = 0"
+ # rubocop:enable Migration/PreventIndexCreation
remove_concurrent_index_by_name :users, OLD_INDEX_NAME
end
diff --git a/db/post_migrate/20230724150939_index_projects_on_namespace_id_and_repository_size_limit.rb b/db/post_migrate/20230724150939_index_projects_on_namespace_id_and_repository_size_limit.rb
index b066cb248fb..fd2387e2bc4 100644
--- a/db/post_migrate/20230724150939_index_projects_on_namespace_id_and_repository_size_limit.rb
+++ b/db/post_migrate/20230724150939_index_projects_on_namespace_id_and_repository_size_limit.rb
@@ -9,7 +9,9 @@ class IndexProjectsOnNamespaceIdAndRepositorySizeLimit < Gitlab::Database::Migra
disable_ddl_transaction!
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects, [:namespace_id, :repository_size_limit], name: INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20230728151058_add_auditor_index_to_users_table.rb b/db/post_migrate/20230728151058_add_auditor_index_to_users_table.rb
index 1a849e7b728..055174b9e32 100644
--- a/db/post_migrate/20230728151058_add_auditor_index_to_users_table.rb
+++ b/db/post_migrate/20230728151058_add_auditor_index_to_users_table.rb
@@ -5,7 +5,9 @@ class AddAuditorIndexToUsersTable < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :users, :id, where: 'auditor IS true', name: INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20230913130629_index_org_id_on_projects.rb b/db/post_migrate/20230913130629_index_org_id_on_projects.rb
index 45186b900c6..c4d3de6c172 100644
--- a/db/post_migrate/20230913130629_index_org_id_on_projects.rb
+++ b/db/post_migrate/20230913130629_index_org_id_on_projects.rb
@@ -6,7 +6,9 @@ class IndexOrgIdOnProjects < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_projects_on_organization_id'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects, :organization_id, name: INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20231003142534_add_build_timeout_index.rb b/db/post_migrate/20231003142534_add_build_timeout_index.rb
index 3a95c7cf748..5820a35eb8d 100644
--- a/db/post_migrate/20231003142534_add_build_timeout_index.rb
+++ b/db/post_migrate/20231003142534_add_build_timeout_index.rb
@@ -6,7 +6,9 @@ class AddBuildTimeoutIndex < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_projects_on_id_where_build_timeout_geq_than_2629746'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects, :id, where: 'build_timeout >= 2629746', name: INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20231009105056_index_users_on_email_domain_and_id.rb b/db/post_migrate/20231009105056_index_users_on_email_domain_and_id.rb
index e6b750ca38b..61aab7cc2c4 100644
--- a/db/post_migrate/20231009105056_index_users_on_email_domain_and_id.rb
+++ b/db/post_migrate/20231009105056_index_users_on_email_domain_and_id.rb
@@ -6,7 +6,9 @@ class IndexUsersOnEmailDomainAndId < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_users_on_email_domain_and_id'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index(:users, "lower(split_part(email, '@', 2)), id", name: INDEX_NAME)
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/post_migrate/20231017172156_add_index_on_projects_for_adjourned_deletion.rb b/db/post_migrate/20231017172156_add_index_on_projects_for_adjourned_deletion.rb
index 6a689a5e11a..9b73035471e 100644
--- a/db/post_migrate/20231017172156_add_index_on_projects_for_adjourned_deletion.rb
+++ b/db/post_migrate/20231017172156_add_index_on_projects_for_adjourned_deletion.rb
@@ -6,10 +6,12 @@ class AddIndexOnProjectsForAdjournedDeletion < Gitlab::Database::Migration[2.1]
INDEX_NAME = 'index_projects_id_for_aimed_for_deletion'
def up
+ # rubocop:disable Migration/PreventIndexCreation
add_concurrent_index :projects,
[:id, :marked_for_deletion_at],
where: 'marked_for_deletion_at IS NOT NULL AND pending_delete = false',
name: INDEX_NAME
+ # rubocop:enable Migration/PreventIndexCreation
end
def down
diff --git a/db/schema_migrations/20231017095738 b/db/schema_migrations/20231017095738
new file mode 100644
index 00000000000..20feb63b199
--- /dev/null
+++ b/db/schema_migrations/20231017095738
@@ -0,0 +1 @@
+730b861c660b96556969054402a7776f622d42ed98055b0f7099c940ecf03c32
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d7d5d469d9e..99141edd313 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10880,6 +10880,30 @@ CREATE SEQUENCE achievements_id_seq
ALTER SEQUENCE achievements_id_seq OWNED BY achievements.id;
+CREATE TABLE activity_pub_releases_subscriptions (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ status smallint DEFAULT 1 NOT NULL,
+ shared_inbox_url text,
+ subscriber_inbox_url text,
+ subscriber_url text NOT NULL,
+ payload jsonb,
+ CONSTRAINT check_0ebf38bcaa CHECK ((char_length(subscriber_inbox_url) <= 1024)),
+ CONSTRAINT check_2afd35ba17 CHECK ((char_length(subscriber_url) <= 1024)),
+ CONSTRAINT check_61b77ced49 CHECK ((char_length(shared_inbox_url) <= 1024))
+);
+
+CREATE SEQUENCE activity_pub_releases_subscriptions_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE activity_pub_releases_subscriptions_id_seq OWNED BY activity_pub_releases_subscriptions.id;
+
CREATE TABLE agent_activity_events (
id bigint NOT NULL,
agent_id bigint NOT NULL,
@@ -25864,6 +25888,8 @@ ALTER TABLE ONLY abuse_trust_scores ALTER COLUMN id SET DEFAULT nextval('abuse_t
ALTER TABLE ONLY achievements ALTER COLUMN id SET DEFAULT nextval('achievements_id_seq'::regclass);
+ALTER TABLE ONLY activity_pub_releases_subscriptions ALTER COLUMN id SET DEFAULT nextval('activity_pub_releases_subscriptions_id_seq'::regclass);
+
ALTER TABLE ONLY agent_activity_events ALTER COLUMN id SET DEFAULT nextval('agent_activity_events_id_seq'::regclass);
ALTER TABLE ONLY agent_group_authorizations ALTER COLUMN id SET DEFAULT nextval('agent_group_authorizations_id_seq'::regclass);
@@ -27632,6 +27658,9 @@ ALTER TABLE ONLY abuse_trust_scores
ALTER TABLE ONLY achievements
ADD CONSTRAINT achievements_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY activity_pub_releases_subscriptions
+ ADD CONSTRAINT activity_pub_releases_subscriptions_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT agent_activity_events_pkey PRIMARY KEY (id);
@@ -31229,6 +31258,10 @@ CREATE INDEX index_abuse_trust_scores_on_user_id_and_source_and_created_at ON ab
CREATE UNIQUE INDEX "index_achievements_on_namespace_id_LOWER_name" ON achievements USING btree (namespace_id, lower(name));
+CREATE UNIQUE INDEX index_activity_pub_releases_sub_on_project_id_inbox_url ON activity_pub_releases_subscriptions USING btree (project_id, lower(subscriber_inbox_url));
+
+CREATE UNIQUE INDEX index_activity_pub_releases_sub_on_project_id_sub_url ON activity_pub_releases_subscriptions USING btree (project_id, lower(subscriber_url));
+
CREATE INDEX index_agent_activity_events_on_agent_id_and_recorded_at_and_id ON agent_activity_events USING btree (agent_id, recorded_at, id);
CREATE INDEX index_agent_activity_events_on_agent_token_id ON agent_activity_events USING btree (agent_token_id) WHERE (agent_token_id IS NOT NULL);
@@ -38269,6 +38302,9 @@ ALTER TABLE ONLY batched_background_migration_jobs
ALTER TABLE ONLY operations_strategies_user_lists
ADD CONSTRAINT fk_rails_43241e8d29 FOREIGN KEY (strategy_id) REFERENCES operations_strategies(id) ON DELETE CASCADE;
+ALTER TABLE ONLY activity_pub_releases_subscriptions
+ ADD CONSTRAINT fk_rails_4337598314 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY analytics_cycle_analytics_value_stream_settings
ADD CONSTRAINT fk_rails_4360d37256 FOREIGN KEY (value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md
index b356d67048d..3bb26681fae 100644
--- a/doc/administration/logs/index.md
+++ b/doc/administration/logs/index.md
@@ -810,8 +810,8 @@ GraphQL queries are recorded in the file. For example:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133371) in GitLab 16.5.
-The `clickhouse.log` file logs information related to
-Clickhouse database client within GitLab.
+The `clickhouse.log` file logs information related to the
+ClickHouse database client in GitLab.
## `migrations.log`
diff --git a/doc/administration/settings/usage_statistics.md b/doc/administration/settings/usage_statistics.md
index 4887ebd8cfe..3c68e943256 100644
--- a/doc/administration/settings/usage_statistics.md
+++ b/doc/administration/settings/usage_statistics.md
@@ -63,6 +63,7 @@ In the following table, you can see:
| [Issue analytics](../../user/group/issues_analytics/index.md) | GitLab 16.5 and later |
| [Custom Text in Emails](../../administration/settings/email.md#custom-additional-text) | GitLab 16.5 and later |
| [Contribution analytics](../../user/group/contribution_analytics/index.md) | GitLab 16.5 and later |
+| [Group file templates](../../user/group/manage.md#group-file-templates) | GitLab 16.6 and later |
### Enable registration features
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6015323f7f7..efe1b606335 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -702,6 +702,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| `creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| `paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| `search` | [`String`](#string) | Filter by full token or partial text in description field. |
| `sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
@@ -18595,6 +18596,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| `creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| `membership` | [`CiRunnerMembershipFilter`](#cirunnermembershipfilter) | Control which runners to include in the results. |
| `paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| `search` | [`String`](#string) | Filter by full token or partial text in description field. |
@@ -23604,6 +23606,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| `active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| `creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| `paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| `search` | [`String`](#string) | Filter by full token or partial text in description field. |
| `sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
diff --git a/doc/development/cells/index.md b/doc/development/cells/index.md
index 30dccd91c9d..1ab88e0d8c6 100644
--- a/doc/development/cells/index.md
+++ b/doc/development/cells/index.md
@@ -16,6 +16,7 @@ To make the application work within the GitLab Cells architecture, we need to fi
Here is the suggested approach:
1. Pick a workflow to fix.
+1. Firstly, we need to find out the tables that are affected while performing the chosen workflow. As an example, in [this note](https://gitlab.com/gitlab-org/gitlab/-/issues/428600#note_1610331742) we have described how to figure out the list of all tables that are affected when a project is created in a group.
1. For each table affected for the chosen workflow, choose the approriate
[GitLab schema](../database/multiple_databases.md#gitlab-schema).
1. Identify all cross-joins, cross-transactions, and cross-database foreign keys for
diff --git a/doc/user/group/saml_sso/troubleshooting.md b/doc/user/group/saml_sso/troubleshooting.md
index 9d3cc0bef50..2fce30b1cc2 100644
--- a/doc/user/group/saml_sso/troubleshooting.md
+++ b/doc/user/group/saml_sso/troubleshooting.md
@@ -356,3 +356,21 @@ If you see this message after trying to invite a user to a group:
1. Ensure the user is a [member of the top-level group](../index.md#search-a-group).
Additionally, see [troubleshooting users receiving a 404 after sign in](#users-receive-a-404).
+
+## Message: The SAML response did not contain an email address. Either the SAML identity provider is not configured to send the attribute, or the identity provider directory does not have an email address value for your user
+
+This error appears when the SAML response does not contain the user's email address in an **email** or **mail** attribute as shown in the following example:
+
+```xml
+
+ user@domain.com‹/AttributeValue>
+
+```
+
+Attribute names starting with phrases such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/` like in the following example are not supported. Remove this type of attribute name from the SAML response on the IDP side.
+
+```xml
+
+ user@domain.com‹/AttributeValue>
+
+```
diff --git a/doc/user/product_analytics/index.md b/doc/user/product_analytics/index.md
index ca55ab758da..ad7367e22c9 100644
--- a/doc/user/product_analytics/index.md
+++ b/doc/user/product_analytics/index.md
@@ -32,7 +32,7 @@ Product analytics uses several tools:
- [**Snowplow**](https://docs.snowplow.io/docs) - A developer-first engine for collecting behavioral data, and passing it through to ClickHouse.
- [**ClickHouse**](https://clickhouse.com/docs) - A database suited to store, query, and retrieve analytical data.
-- [**Cube**](https://cube.dev/docs/) - An analytical graphing library that provides an API to run queries against the data stored in Clickhouse.
+- [**Cube**](https://cube.dev/docs/) - An analytical graphing library that provides an API to run queries against the data stored in ClickHouse.
The following diagram illustrates the product analytics flow:
@@ -46,7 +46,7 @@ flowchart TB
B --Pass data through--> C[Snowplow Enricher]
end
subgraph Data warehouse
- C --Transform and enrich data--> D([Clickhouse])
+ C --Transform and enrich data--> D([ClickHouse])
end
subgraph Data visualization with dashboards
E([Dashboards]) --Generated from the YAML definition--> F[Panels/Visualizations]
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index 7de8a7beab5..3526425c912 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -60,7 +60,7 @@ To create a project access token:
1. Enter a name. The token name is visible to any user with permissions to view the project.
1. Enter an expiry date for the token.
- The token expires on that date at midnight UTC.
- - If you do not enter an expiry date, the expiry date is automatically set to 365 days later than the current date.
+ - If you do not enter an expiry date, the expiry date is automatically set to 30 days later than the current date.
- By default, this date can be a maximum of 365 days later than the current date.
- An instance-wide [maximum lifetime](../../../administration/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) setting can limit the maximum allowable lifetime in self-managed instances.
1. Select a role for the token.
diff --git a/doc/user/report_abuse.md b/doc/user/report_abuse.md
index 45113562e87..9e13d1fe263 100644
--- a/doc/user/report_abuse.md
+++ b/doc/user/report_abuse.md
@@ -26,17 +26,12 @@ You can report a user through their:
> - Report abuse from overflow menu [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414773) in GitLab 16.4 [with a flag](../administration/feature_flags.md) named `user_profile_overflow_menu_vue`. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/414773) in GitLab 16.4.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `user_profile_overflow_menu_vue`.
-On GitLab.com, this feature is available.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/414773) in GitLab 16.6. Feature flag `user_profile_overflow_menu_vue` removed.
To report abuse from a user's profile page:
1. Anywhere in GitLab, select the name of the user.
-1. In the upper-right corner of the user's profile, if the `user_profile_overflow_menu_vue` feature flag is:
- - Enabled, select the vertical ellipsis (**{ellipsis_v}**), then **Report abuse to administrator**.
- - Disabled, select **Report abuse to administrator** (**{information-o}**).
+1. In the upper-right corner of the user's profile select the vertical ellipsis (**{ellipsis_v}**), then **Report abuse to administrator**.
1. Select a reason for reporting the user.
1. Complete an abuse report.
1. Select **Send report**.
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index e8dfbfa675a..0b40217b3c8 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -112,19 +112,16 @@ To include archived projects:
1. On the project search page, on the left sidebar, select the **Include archived** checkbox.
1. On the left sidebar, select **Apply**.
-## Exclude issues in archived projects from search results
+### Include issues in archived projects
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124846) in GitLab 16.2 [with a flag](../../administration/feature_flags.md) named `search_issues_hide_archived_projects`. Disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124846) in GitLab 16.2 [with a flag](../../administration/feature_flags.md) named `search_issues_hide_archived_projects`. Disabled by default.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/416483) in GitLab 16.6. Feature flag `search_issues_hide_archived_projects` removed.
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-an administrator can [enable the feature flag](../../administration/feature_flags.md) named `search_issues_hide_archived_projects`. On GitLab.com, this feature is not available.
+By default, issues in archived projects are excluded from search results.
+To include issues in archived projects:
-By default, issues in archived projects are included in search results.
-To exclude issues in archived projects, ensure the `search_issues_hide_archived_projects` flag is enabled.
-
-To include issues in archived projects with `search_issues_hide_archived_projects` enabled,
-you must add the parameter `include_archived=true` to the URL.
+1. On the project search page, on the left sidebar, select the **Include archived** checkbox.
+1. On the left sidebar, select **Apply**.
## Search for code
diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb
index 88734ac1186..d0e9a9afc51 100644
--- a/lib/gitlab/ci/config/entry/processable.rb
+++ b/lib/gitlab/ci/config/entry/processable.rb
@@ -25,6 +25,8 @@ module Gitlab
validates :name, type: Symbol
validates :name, length: { maximum: 255 }
+ validates :config, mutually_exclusive_keys: %i[script trigger]
+
validates :config, disallowed_keys: {
in: %i[only except start_in],
message: 'key may not be used with `rules`',
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index 87b7cab3f6d..c7dd11b0432 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -56,8 +56,7 @@ module Gitlab
mutually_exclusive_keys = value.try(:keys).to_a & options[:in]
if mutually_exclusive_keys.length > 1
- record.errors.add(attribute, "please use only one of the following keys: " +
- mutually_exclusive_keys.join(', '))
+ record.errors.add(attribute, "these keys cannot be used together: #{mutually_exclusive_keys.join(', ')}")
end
end
end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb
new file mode 100644
index 00000000000..583aceba098
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch < Base
+ SetOperatorStarError = Class.new(QueryAnalyzerError)
+
+ DETECT_REGEX = /.*SELECT.+(UNION|EXCEPT|INTERSECT)/i
+
+ class << self
+ def enabled?
+ ::Feature::FlipperFeature.table_exists? &&
+ Feature.enabled?(:query_analyzer_gitlab_schema_metrics, type: :ops)
+ end
+
+ def analyze(parsed)
+ return unless requires_detection?(parsed.sql)
+
+ # Only handle SELECT queries.
+ parsed.pg.tree.stmts.each do |stmt|
+ select_stmt = next_select_stmt(stmt)
+ next unless select_stmt
+
+ types = SelectStmt.new(select_stmt).types
+
+ raise SetOperatorStarError if types.any?(Type::INVALID)
+ end
+ end
+
+ private
+
+ def next_select_stmt(node)
+ return unless node.stmt.respond_to?(:select_stmt)
+
+ node.stmt.select_stmt
+ end
+
+ # This not entirely correct and will run true on `SELECT union_station, ...`
+ def requires_detection?(sql)
+ sql.match DETECT_REGEX
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns.rb
new file mode 100644
index 00000000000..87120b8ffce
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ # Columns refer to table columns produced by queries and parts of queries.
+ # If we have `SELECT namespaces.id` then `id` is a column. But, we can also have
+ # `WHERE namespaces.id > 10` and `id` is also a column.
+ #
+ # In static analysis of a SQL query a column source can be ambiguous.
+ # Such as in `SELECT id FROM users, namespaces. In such cases we assume `id` could come from either `users` or
+ # `namespaces`.
+ class Columns
+ class << self
+ # Determine the type of each column in the select statement.
+ # Returns a Set object containing a Types enum.
+ # When an error is found parsing will return immediately.
+ def types(select_stmt)
+ # Forward through any errors when the column refers to a part of the SQL query that is known to include
+ # errors. For example, the column may refer to a column from a CTE that was invalid.
+ return Set.new([Type::INVALID]) if References.errors?(select_stmt.all_references)
+
+ types = Set.new
+
+ # Resolve the type of reference for each target in the select statement.
+ target_list = select_stmt.node.target_list
+ targets = target_list.map(&:res_target)
+ targets.each do |target|
+ target_type = get_target_type(target, select_stmt)
+
+ # A NULL target is of the form:
+ # SELECT NULL::namespaces FROM namespaces
+ types += if Targets.null?(target)
+ # Maintain any errors but otherwise ignore this target.
+ target_type & [Type::INVALID]
+ else
+ target_type
+ end
+ end
+
+ types
+ end
+
+ private
+
+ def get_target_type(target, select_stmt)
+ target_ref_names = Targets.reference_names(target, select_stmt)
+
+ resolved_refs = References.resolved(select_stmt.all_references)
+
+ # Cross reference column references with resolved references.
+ # A resolved reference is part of a SQL query that we were able to analyze already.
+ # A CTE or sub-query would be such a case. The only non-resolvable reference is a table.
+ all_resolved = (target_ref_names - resolved_refs.keys).empty?
+
+ # Is this target `*` such as `SELECT *`.
+ a_star = Targets.a_star?(target)
+
+ if all_resolved
+ # Defer to the reference source types.
+ col_refs = resolved_refs.slice(*target_ref_names)
+ .values
+ .reduce(:union) || Set.new
+
+ if a_star
+ # When * the target forwards through the types of the references.
+ col_refs
+ else
+ # When not * the column is static, but we also forward through any nested errors.
+ (col_refs.to_a & [Type::INVALID]) << Type::STATIC
+ end
+ elsif a_star
+ # This is a * on a table. The * lookup occurs dynamically during query runtime and will
+ # change when the table schema changes.
+ [Type::DYNAMIC]
+ else
+ # This references a column on a table or intermediate result set such as:
+ # SELECT namespaces.id FROM namespaces
+ #
+ # or:
+ # WITH some_cte AS ( ... ) SELECT some_cte.id FROM some_cte
+ [Type::STATIC]
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions.rb
new file mode 100644
index 00000000000..0ab58ff7c6f
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+# The CTE in a SELECT can reference CTEs defined by the current scope, but also CTEs defined by earlier scopes.
+# With the following query as an example:
+#
+# WITH some_cte AS (select 1)
+# SELECT *
+# FROM (SELECT * FROM some_cte) subquery
+#
+# The CTE some_cte is visible from within the subquery scope.
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ class CommonTableExpressions
+ class << self
+ # Convert CTEs available within this SELECT statement into a set of References.
+ #
+ # @param [PgQuery::Node] node The PgQuery SELECT statement node containing the CTEs.
+ # @param [References] cte_refs Inherited CTEs from scopes that wrap this SELECT statement.
+ def references(node, cte_refs)
+ return cte_refs if node&.with_clause.nil?
+
+ refs = cte_refs.dup
+
+ node.with_clause.ctes.each do |cte|
+ cte_name = name(cte)
+ cte_select_stmt = select_stmt(cte)
+
+ # Resolve the CTE type to dynamic/static/error.
+ refs[cte_name] = if node.with_clause.recursive
+ # Recursive CTEs need special handling to avoid infinite loops.
+ recursive_refs(cte_refs, cte_name, cte_select_stmt)
+ else
+ SelectStmt.new(cte_select_stmt, cte_refs).types
+ end
+ end
+
+ refs
+ end
+
+ private
+
+ def name(cte)
+ cte.common_table_expr.ctename
+ end
+
+ def select_stmt(cte)
+ cte.common_table_expr.ctequery.select_stmt
+ end
+
+ # Return whether the recursive CTE is dynamic/static/error.
+ def recursive_refs(cte_refs, cte_name, select_stmt)
+ # Resolve the non-recursive term before the recursive term.
+ larg_select_stmt = SelectStmt.new(select_stmt.larg, cte_refs)
+ larg_type = larg_select_stmt.types
+ new_cte_refs = cte_refs.merge({ cte_name => larg_type })
+
+ # Now we can resolve the recursive side.
+ rarg_type = SelectStmt.new(select_stmt.rarg, new_cte_refs).types
+
+ final_type = larg_type | rarg_type
+ if final_type.count > 1
+ final_type | [Type::INVALID]
+ else
+ final_type
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms.rb
new file mode 100644
index 00000000000..c205243694a
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ class Froms
+ class << self
+ # Parse the FROM part of the SELECT. Construct a mapping of FROM names to their PgQuery node. Recurse any
+ # sub-queries and resolve to a Set of dynamic/static/error.
+ #
+ # Whenever a node is aliased, use the alias name as it's reference and ignore it's original name.
+ #
+ # For example, given:
+ #
+ # SELECT id
+ # FROM namespaces ns
+ #
+ # Return a Hash of { 'ns' => NodeObject }
+ #
+ # @param [PgQuery::Node] node The PgQuery SELECT statement node containing the CTEs.
+ # @param [References] cte_refs Inherited CTEs from scopes that wrap this SELECT statement.
+ #
+ # @return [Hash] name of from references mapped to the node that defines their value, or Set if already
+ # resolved.
+ def references(node, cte_refs)
+ refs = {}
+
+ return refs unless node
+
+ node.from_clause.each do |from|
+ range_var = Node.dig(from, :range_var)
+ range_sq = Node.dig(from, :range_subselect)
+
+ if range_var
+ # FROM some_table
+ # FROM some_table some_alias
+ refs.merge!(range_var_reference(range_var, cte_refs))
+ elsif Node.dig(from, :join_expr)
+ # FROM some_table INNER JOIN other_table
+ range_vars = Node.locate_descendants(from, :range_var)
+ range_vars.each do |range_var|
+ refs.merge!(range_var_reference(range_var, cte_refs))
+ end
+ elsif range_sq
+ # FROM (SELECT ...) some_alias
+ select_stmt = Node.dig(range_sq, :subquery, :select_stmt)
+ refs[range_sq.alias.aliasname] = SelectStmt.new(select_stmt, cte_refs).types
+ end
+ end
+
+ refs
+ end
+
+ private
+
+ def range_var_reference(range_var, cte_refs)
+ relname = Node.dig(range_var, :alias, :aliasname) || range_var.relname
+ reference = cte_refs[range_var.relname] || range_var
+
+ { relname => reference }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb
new file mode 100644
index 00000000000..ee41eaa9d3a
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ # The Node class allows us to traverse PgQuery nodes with tree like semantics.
+ #
+ # This class balances convenience and performance. The PgQuery nodes are Google::Protobuf::MessageExts which
+ # contain a dynamic set of attributes known as fields. Accessing these fields can cause performance problems
+ # due to the large volume of iterable fields.
+ #
+ # When possible use #dig over the *descendant* methods.
+ #
+ # The filter available to each method reduces the traversed attributes. The default filter only traverses nodes
+ # required to parse for set operator mismatches.
+ class Node
+ class << self
+ include Gitlab::Utils::StrongMemoize
+
+ # The default nodes help speed up traversal. Traversal of other nodes can greatly affect performance.
+ DEFAULT_NODES = %i[
+ a_star
+ alias
+ args
+ column_ref
+ fields
+ func_call
+ join_expr
+ larg
+ range_subselect
+ range_var
+ rarg
+ res_target
+ subquery
+ val
+ ].freeze
+ DEFAULT_FIELD_FILTER = ->(field) { field.is_a?(Integer) || DEFAULT_NODES.include?(field) }.freeze
+
+ # Recurse through children.
+ # The block will yield the child node and the name of that node.
+ # Calling without a block will return an Enumerator.
+ def descendants(node, filter: DEFAULT_FIELD_FILTER, &blk)
+ if blk
+ children(node, filter: filter) do |child_node, child_field|
+ yield(child_node, child_field)
+
+ descendants(child_node, filter: filter, &blk)
+ end
+ nil
+ else
+ enum_for(:descendants, node, filter: filter, &blk)
+ end
+ end
+
+ # Return the first node that matches the field.
+ def locate_descendant(node, field, filter: DEFAULT_FIELD_FILTER)
+ descendants(node, filter: filter).find { |_, child_field| child_field == field }&.first
+ end
+
+ # Return all nodes that match the field.
+ def locate_descendants(node, field, filter: DEFAULT_FIELD_FILTER)
+ descendants(node, filter: filter).select { |_, child_field| child_field == field }.map(&:first)
+ end
+
+ # Like Hash#dig, traverse attributes in sequential order and return the final value.
+ # Return nil if any of the fields are not available.
+ def dig(node, *attrs)
+ obj = node
+ attrs.each do |attr|
+ if obj.respond_to?(attr)
+ obj = obj.public_send(attr) # rubocop:disable GitlabSecurity/PublicSend
+ else
+ obj = nil
+ break
+ end
+ end
+ obj
+ end
+
+ private
+
+ # Interface with a PgQuery result as though it was a tree node.
+ # All elements in a PgQuery result are ancestors of Google::Protobuf::AbstractMessage
+ #
+ # Based off PgQuery's treewalker https://github.com/pganalyze/pg_query/blob/main/lib/pg_query/treewalker.rb
+ def children(node, filter: DEFAULT_FIELD_FILTER, &_blk)
+ attributes = case node
+ when Google::Protobuf::MessageExts
+ descriptor_fields(node.class.descriptor)
+ when Google::Protobuf::RepeatedField
+ node.count.times.to_a
+ end
+
+ attributes.select(&filter).each do |attr|
+ attr_key = attr.is_a?(Symbol) ? attr.to_s : attr
+ child = node[attr_key]
+ next if child.nil?
+
+ yield(child, attr)
+ end
+ end
+
+ def descriptor_fields(descriptor)
+ strong_memoize_with(:descriptor_fields, descriptor) do
+ keys = []
+ descriptor.each do |field|
+ keys << field.name.to_sym
+ end
+ keys
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references.rb
new file mode 100644
index 00000000000..ba6e9752905
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# References form the base data structure of the PreventSetOperatorMismatch query analyzer.
+#
+# A reference refers to a table, CTE, or other named entity in a SQL query. References are a set of mappings between the
+# name of the reference and the PgQuery node that represents that reference in the parsed tree.
+#
+# Given the SQL:
+#
+# WITH some_cte AS (SELECT 1)
+# SELECT *
+# FROM some_cte, users, namespace ns
+#
+# The reference names would be `some_cte`, `users`, `ns`. The reference values are the nodes in the parse tree that
+# represent that reference:
+# - some_cte: the common table expression node
+# - users: nil, being a table
+# - ns: nil, being a table, but importantly we use the alias name
+#
+# A reference can be "resolved". A resolved reference value is a Set of Types. The reference value was a select
+# statement that has since been parsed.
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ class References
+ class << self
+ # All references that have already been parsed to determine static/dynamic/error state.
+ # @param [Hash] refs A Hash of reference names mapped to the parse tree node or resolved Set of Types.
+ def resolved(refs)
+ refs.select { |_name, ref| ref.is_a?(Set) }
+ end
+
+ # All references that have not been parsed to determine static/dynamic/error state.
+ # @param [Hash] refs A Hash of reference names mapped to the parse tree node or resolved Set of Types.
+ def unresolved(refs)
+ refs.select { |_name, ref| unresolved?(ref) }
+ end
+
+ # Whether any currently resolved references have resulted in an error state.
+ # @param [Hash] refs A Hash of reference names mapped to the parse tree node or resolved Set of Types.
+ def errors?(refs)
+ resolved(refs).any? { |_, values| values.include?(Type::INVALID) }
+ end
+
+ private
+
+ def resolved?(ref)
+ ref.is_a?(Set)
+ end
+
+ def unresolved?(ref)
+ !resolved?(ref) && table?(ref)
+ end
+
+ def table?(ref)
+ !ref.is_a?(PgQuery::RangeVar)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt.rb
new file mode 100644
index 00000000000..bdbcc49f63f
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ class SelectStmt
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :node, :cte_references, :all_references
+
+ # @param [PgQuery::SelectStmt] node The PgQuery node of the select statement.
+ # @param [Hash] inherited_cte_references CTE References available to the select statement.
+ def initialize(node, inherited_cte_references = {})
+ @node = node
+ @cte_references = CommonTableExpressions.references(node, inherited_cte_references)
+ from_references = Froms.references(node, cte_references)
+ @all_references = from_references.merge(cte_references)
+ end
+
+ # returns Set of Types.
+ #
+ # STATIC - queries that don't require a database schema lookup. E.g. `SELECT users.id FROM users`
+ # DYNAMIC - queries that require a database schema lookup. E.g. `SELECT users.* FROM users`
+ # INVALID - set operator queries that mix static and dynamic queries.
+ def types
+ if set_operator?
+ resolve_set_operator_select_types
+ else
+ resolve_normal_select_types
+ end
+ end
+
+ private
+
+ # Standard SELECT, not a set operator (UNION/INTERSECT/EXCEPT)
+ def resolve_normal_select_types
+ # Cross reference resolved sources with what is requested by the SELECT.
+ types = Columns.types(self)
+
+ # Mixed dynamic and static queries can be normalized to simply dynamic queries for the purposes of
+ # detecting mismatched set operator parts.
+ types.delete(Type::STATIC) if types.include?(Type::DYNAMIC)
+
+ types
+ end
+
+ # Set operator (UNION/INTERSECT/EXCEPT)
+ def resolve_set_operator_select_types
+ types = Set.new
+
+ # Recurse each set operator part as a SELECT statement.
+ # select statement part => type
+ set_operator_parts do |part|
+ types += SelectStmt.new(part, cte_references).types
+ end
+
+ types << Type::INVALID if types.count > 1
+
+ types
+ end
+
+ def set_operator?
+ !(node.respond_to?(:op) && node.op == :SETOP_NONE)
+ end
+
+ SET_OPERATOR_PART_LOCATIONS = %i[larg rarg].freeze
+ private_constant :SET_OPERATOR_PART_LOCATIONS
+
+ def set_operator_parts(&_blk)
+ return unless node
+
+ yield node if node.op == :SETOP_NONE
+ yield node.larg if node.larg && node.larg.op == :SETOP_NONE
+ yield node.rarg if node.rarg && node.rarg.op == :SETOP_NONE
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets.rb
new file mode 100644
index 00000000000..99db368efcb
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+# Targets refer to SELECT columns but also JOIN fields, etc.
+# A target can have a qualifying reference to some other entity like a table or CTE.
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ class Targets
+ class << self
+ # Return the reference names used by the given target.
+ #
+ # For example:
+ # `SELECT users.id` would return ['users']
+ # `SELECT * FROM users, namespaces` would return ['users', 'namespaces']
+ def reference_names(target, select_stmt)
+ # Parse all targets to determine what is referenced.
+ fields = fields(target)
+ case fields.count
+ when 0
+ literal_ref_names(target, select_stmt)
+ when 1
+ unqualified_ref_names(fields, select_stmt)
+ else
+ # The target is qualified such as SELECT reference.id
+ field_ref = fields[fields.count - 2]
+ [field_ref.string.sval]
+ end
+ end
+
+ # True when `SELECT *`
+ def a_star?(target)
+ Node.locate_descendant(target, :a_star)
+ end
+
+ # Null targets are used to produce "polymorphic" query result sets that can be aggregated through a UNION
+ # without having to worry about mismatched columns.
+ #
+ # A null target would be something like:
+ # SELECT NULL::namespaces FROM namespaces
+ def null?(target)
+ target&.val&.type_cast&.arg&.a_const&.isnull
+ end
+
+ private
+
+ def literal_ref_names(target, select_stmt)
+ # The target is unqualified and is not part of a column_ref, such as in `SELECT 1`.
+ # These include targets like literals, functions, and subselects.
+ sub_select_stmt = subselect_select_stmt(target)
+ if sub_select_stmt
+ name = (target.name.presence || "loc_#{target.location}")
+ # The select is anonymous, so we provide a name.
+ k = "#{name}_subselect"
+ # Force parsing of the select.
+ # We don't care about the static/dynamic nature in this case, but we do need to parse for
+ # any nested error states.
+ sub_select = SelectStmt.new(sub_select_stmt, select_stmt.cte_references)
+ select_stmt.all_references[k] = sub_select.types
+ [k]
+ else
+ # TODO we need to parse function references. Assuming no sources for now.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/428102
+ []
+ end
+ end
+
+ def unqualified_ref_names(fields, select_stmt)
+ # The target is unqualified, but is part of a column_ref.
+ # E.g. `SELECT id FROM namespaces` or `SELECT namespaces FROM namespaces`
+
+ # Otherwise, check all FROM/JOIN/CTE entries.
+ field = fields[0]
+ field_sval = field&.string&.sval
+ if field_sval && select_stmt.all_references.key?(field_sval)
+ # SELECT some_table_name
+ [field.string.sval]
+ else
+ # SELECT *
+ # SELECT some_column
+ select_stmt.all_references.keys
+ end
+ end
+
+ def fields(target)
+ Node.locate_descendants(target, :fields).flatten
+ end
+
+ def subselect_select_stmt(target)
+ Node.dig(target, :val, :sub_link, :subselect, :select_stmt)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/type.rb b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/type.rb
new file mode 100644
index 00000000000..5988f963827
--- /dev/null
+++ b/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module QueryAnalyzers
+ class PreventSetOperatorMismatch
+ # An enumerated set of constants that represent the state of the parse.
+ module Type
+ STATIC = :static
+ DYNAMIC = :dynamic
+ INVALID = :invalid
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index e057b4bb6f1..f491b00c461 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -75,7 +75,6 @@ module Gitlab
push_frontend_feature_flag(:security_auto_fix)
push_frontend_feature_flag(:source_editor_toolbar)
push_frontend_feature_flag(:vscode_web_ide, current_user)
- push_frontend_feature_flag(:unbatch_graphql_queries, current_user)
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/399248
push_frontend_feature_flag(:remove_monitor_metrics)
push_frontend_feature_flag(:custom_emoji)
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index d06f414bd9a..97554ca8f15 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -191,9 +191,7 @@ module Gitlab
unless default_project_filter
project_ids = project_ids_relation
- if Feature.enabled?(:search_issues_hide_archived_projects, current_user) && !filters[:include_archived]
- project_ids = project_ids.non_archived
- end
+ project_ids = project_ids.non_archived unless filters[:include_archived]
issues = issues.in_projects(project_ids)
.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/420046')
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f595aa585ea..60b0851f08c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -51733,9 +51733,6 @@ msgstr ""
msgid "UserProfile|Contributed projects"
msgstr ""
-msgid "UserProfile|Copy user ID"
-msgstr ""
-
msgid "UserProfile|Copy user ID: %{id}"
msgstr ""
@@ -51838,9 +51835,6 @@ msgstr ""
msgid "UserProfile|User ID copied to clipboard"
msgstr ""
-msgid "UserProfile|User ID: %{id}"
-msgstr ""
-
msgid "UserProfile|User profile navigation"
msgstr ""
diff --git a/package.json b/package.json
index 33826889ae4..06437c6c6b3 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.66.0",
- "@gitlab/ui": "66.33.0",
+ "@gitlab/ui": "66.34.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20231004090414",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/rubocop/cop/migration/prevent_index_creation.rb b/rubocop/cop/migration/prevent_index_creation.rb
index aa0ab7b1e50..0a1dccccbc8 100644
--- a/rubocop/cop/migration/prevent_index_creation.rb
+++ b/rubocop/cop/migration/prevent_index_creation.rb
@@ -8,11 +8,11 @@ module RuboCop
class PreventIndexCreation < RuboCop::Cop::Base
include MigrationHelpers
- FORBIDDEN_TABLES = %i[ci_builds namespaces].freeze
+ FORBIDDEN_TABLES = %i[ci_builds namespaces projects users].freeze
MSG = "Adding new index to #{FORBIDDEN_TABLES.join(", ")} is forbidden. " \
"For `ci_builds` see https://gitlab.com/gitlab-org/gitlab/-/issues/332886, " \
- "for `namespaces` see https://gitlab.com/groups/gitlab-org/-/epics/11543".freeze
+ "for `namespaces`, `projects`, and `users` see https://gitlab.com/groups/gitlab-org/-/epics/11543".freeze
def on_new_investigation
super
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 94aedf463e9..9453520341b 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -537,6 +537,34 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(response.headers['Cache-Control']).to eq('max-age=60, private')
expect(response.headers['Pragma']).to be_nil
end
+
+ context 'unique users tracking' do
+ before do
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ end
+
+ it_behaves_like 'tracking unique hll events' do
+ subject(:request) { get :autocomplete, params: { term: 'term' } }
+
+ let(:target_event) { 'i_search_total' }
+ let(:expected_value) { instance_of(String) }
+ end
+ end
+
+ it_behaves_like 'Snowplow event tracking with RedisHLL context' do
+ subject { get :autocomplete, params: { group_id: namespace.id, term: 'term' } }
+
+ let(:project) { nil }
+ let(:category) { described_class.to_s }
+ let(:action) { 'autocomplete' }
+ let(:label) { 'redis_hll_counters.search.search_total_unique_counts_monthly' }
+ let(:property) { 'i_search_total' }
+ let(:context) do
+ [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property).to_context]
+ end
+
+ let(:namespace) { create(:group) }
+ end
end
describe '#append_info_to_payload' do
diff --git a/spec/factories/activity_pub/releases_subscriptions.rb b/spec/factories/activity_pub/releases_subscriptions.rb
new file mode 100644
index 00000000000..b789188528a
--- /dev/null
+++ b/spec/factories/activity_pub/releases_subscriptions.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :activity_pub_releases_subscription, class: 'ActivityPub::ReleasesSubscription' do
+ project
+ subscriber_url { 'https://example.com/actor' }
+ status { :requested }
+ payload do
+ {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: 'https://example.com/actor#follow/1',
+ type: 'Follow',
+ actor: 'https://example.com/actor',
+ object: 'http://localhost/user/project/-/releases'
+ }
+ end
+
+ trait :inbox do
+ subscriber_inbox_url { 'https://example.com/actor/inbox' }
+ end
+
+ trait :shared_inbox do
+ shared_inbox_url { 'https://example.com/shared-inbox' }
+ end
+ end
+end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index f1df5c2d6f0..50d64ce533c 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -55,103 +55,53 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
it_behaves_like 'reports the user with an abuse category'
end
- describe 'when user_profile_overflow_menu FF turned on' do
- context 'when reporting a user profile for abuse' do
- let_it_be(:reporter2) { create(:user, :no_super_sidebar) }
+ context 'when reporting a user profile for abuse' do
+ let_it_be(:reporter2) { create(:user, :no_super_sidebar) }
- before do
- visit user_path(abusive_user)
- find_by_testid('base-dropdown-toggle').click
- end
-
- it_behaves_like 'reports the user with an abuse category'
-
- it 'allows the reporter to report the same user for different abuse categories' do
- visit user_path(abusive_user)
-
- find_by_testid('base-dropdown-toggle').click
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
-
- visit user_path(abusive_user)
-
- find_by_testid('base-dropdown-toggle').click
- fill_and_submit_abuse_category_form("They're being offensive or abusive.")
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
- end
-
- it 'allows multiple users to report the same user' do
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
-
- gitlab_sign_out
- gitlab_sign_in(reporter2)
-
- visit user_path(abusive_user)
-
- find_by_testid('base-dropdown-toggle').click
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
- end
-
- it_behaves_like 'cancel report'
+ before do
+ visit user_path(abusive_user)
+ find_by_testid('base-dropdown-toggle').click
end
- end
- describe 'when user_profile_overflow_menu FF turned off' do
- context 'when reporting a user profile for abuse' do
- let_it_be(:reporter2) { create(:user, :no_super_sidebar) }
+ it_behaves_like 'reports the user with an abuse category'
- before do
- stub_feature_flags(user_profile_overflow_menu_vue: false)
- visit user_path(abusive_user)
- end
+ it 'allows the reporter to report the same user for different abuse categories' do
+ visit user_path(abusive_user)
- it_behaves_like 'reports the user with an abuse category'
+ find_by_testid('base-dropdown-toggle').click
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- it 'allows the reporter to report the same user for different abuse categories' do
- visit user_path(abusive_user)
+ expect(page).to have_content 'Thank you for your report'
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
+ visit user_path(abusive_user)
- expect(page).to have_content 'Thank you for your report'
+ find_by_testid('base-dropdown-toggle').click
+ fill_and_submit_abuse_category_form("They're being offensive or abusive.")
+ fill_and_submit_report_abuse_form
- visit user_path(abusive_user)
-
- fill_and_submit_abuse_category_form("They're being offensive or abusive.")
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
- end
-
- it 'allows multiple users to report the same user' do
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
-
- gitlab_sign_out
- gitlab_sign_in(reporter2)
-
- visit user_path(abusive_user)
-
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
-
- expect(page).to have_content 'Thank you for your report'
- end
-
- it_behaves_like 'cancel report'
+ expect(page).to have_content 'Thank you for your report'
end
+
+ it 'allows multiple users to report the same user' do
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+
+ gitlab_sign_out
+ gitlab_sign_in(reporter2)
+
+ visit user_path(abusive_user)
+
+ find_by_testid('base-dropdown-toggle').click
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+ end
+
+ it_behaves_like 'cancel report'
end
context 'when reporting an merge request for abuse' do
@@ -180,10 +130,6 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
end
end
- # TODO: implement tests before the FF "user_profile_overflow_menu_vue" is turned on
- # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971
- # Related Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/416983
-
private
def fill_and_submit_abuse_category_form(category = "They're posting spam.")
diff --git a/spec/features/merge_request/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb
index 8c782056aa4..c2f82039f0b 100644
--- a/spec/features/merge_request/user_reverts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb
@@ -3,23 +3,28 @@
require 'spec_helper'
RSpec.describe 'User reverts a merge request', :js, feature_category: :code_review_workflow do
+ include Spec::Support::Helpers::ModalHelpers
+
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:project) { create(:project, :public, :repository) }
let(:user) { create(:user) }
before do
- stub_feature_flags(unbatch_graphql_queries: false)
project.add_developer(user)
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
visit(merge_request_path(merge_request))
page.within('.mr-state-widget') do
click_button 'Merge'
end
- wait_for_requests
+ wait_for_all_requests
+ page.refresh
+
+ wait_for_requests
# do not reload the page by visiting, let javascript update the page as it will validate we have loaded the modal
# code correctly on page update that adds the `revert` button
end
@@ -55,11 +60,11 @@ RSpec.describe 'User reverts a merge request', :js, feature_category: :code_revi
end
def revert_commit(create_merge_request: false)
- click_button('Revert')
+ click_button 'Revert'
- page.within('[data-testid="modal-commit"]') do
+ within_modal do
uncheck('create_merge_request') unless create_merge_request
- click_button('Revert')
+ click_button 'Revert'
end
end
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 96cad397441..18537d1cddb 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -53,7 +53,6 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
let!(:deployment) { build.deployment }
before do
- stub_feature_flags(unbatch_graphql_queries: false)
merge_request.update!(head_pipeline: pipeline)
deployment.update!(status: :success)
visit project_merge_request_path(project, merge_request)
@@ -84,6 +83,8 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
wait_for_requests
+ page.refresh
+
click_button 'Cherry-pick'
page.within(modal_selector) do
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index fdeee6a2808..ebb84a0d87f 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -12,6 +12,8 @@ RSpec.describe 'Merge request > User sees revert modal', :js, :sidekiq_might_not
it 'shows the revert modal' do
click_button('Revert')
+ wait_for_requests
+
page.within('[data-testid="modal-commit"]') do
expect(page).to have_content 'Revert this merge request'
end
@@ -19,7 +21,6 @@ RSpec.describe 'Merge request > User sees revert modal', :js, :sidekiq_might_not
end
before do
- stub_feature_flags(unbatch_graphql_queries: false)
sign_in(user)
visit(project_merge_request_path(project, merge_request))
@@ -27,6 +28,10 @@ RSpec.describe 'Merge request > User sees revert modal', :js, :sidekiq_might_not
click_button 'Merge'
end
+ wait_for_all_requests
+
+ page.refresh
+
wait_for_requests
end
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 99451ac472d..6cc081d2b65 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -6,55 +6,29 @@ RSpec.describe 'User RSS', feature_category: :user_profile do
let(:user) { create(:user, :no_super_sidebar) }
let(:path) { user_path(create(:user, :no_super_sidebar)) }
- describe 'with "user_profile_overflow_menu_vue" feature flag off' do
+ context 'when signed in' do
before do
- stub_feature_flags(user_profile_overflow_menu_vue: false)
+ sign_in(user)
+ visit path
end
- context 'when signed in' do
- before do
- sign_in(user)
- visit path
- end
+ it 'shows the RSS link with overflow menu', :js do
+ find('[data-testid="base-dropdown-toggle"').click
- it_behaves_like "it has an RSS button with current_user's feed token"
- end
-
- context 'when signed out' do
- before do
- stub_feature_flags(super_sidebar_logged_out: false)
- visit path
- end
-
- it_behaves_like "it has an RSS button without a feed token"
+ expect(page).to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
end
end
- describe 'with "user_profile_overflow_menu_vue" feature flag on', :js do
- context 'when signed in' do
- before do
- sign_in(user)
- visit path
- end
-
- it 'shows the RSS link with overflow menu' do
- find('[data-testid="base-dropdown-toggle"').click
-
- expect(page).to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
- end
+ context 'when signed out' do
+ before do
+ stub_feature_flags(super_sidebar_logged_out: false)
+ visit path
end
- context 'when signed out' do
- before do
- stub_feature_flags(super_sidebar_logged_out: false)
- visit path
- end
+ it 'has an RSS without a feed token', :js do
+ find('[data-testid="base-dropdown-toggle"').click
- it 'has an RSS without a feed token' do
- find('[data-testid="base-dropdown-toggle"').click
-
- expect(page).not_to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
- end
+ expect(page).not_to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
end
end
end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index 522eb12f507..a638dad7edc 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -13,32 +13,12 @@ RSpec.describe 'User page', feature_category: :user_profile do
subject(:visit_profile) { visit(user_path(user)) }
- context 'with "user_profile_overflow_menu_vue" feature flag enabled', :js do
- it 'does not show the user id in the profile info' do
- subject
+ it 'shows copy user id action in the dropdown', :js do
+ subject
- expect(page).not_to have_content("User ID: #{user.id}")
- end
+ find('[data-testid="base-dropdown-toggle"').click
- it 'shows copy user id action in the dropdown' do
- subject
-
- find('[data-testid="base-dropdown-toggle"').click
-
- expect(page).to have_content("Copy user ID: #{user.id}")
- end
- end
-
- context 'with "user_profile_overflow_menu_vue" feature flag disabled', :js do
- before do
- stub_feature_flags(user_profile_overflow_menu_vue: false)
- end
-
- it 'shows user id' do
- subject
-
- expect(page).to have_content("User ID: #{user.id}")
- end
+ expect(page).to have_content("Copy user ID: #{user.id}")
end
it 'shows name on breadcrumbs' do
diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb
index 06cca035c6f..57a77b08cea 100644
--- a/spec/finders/ci/runners_finder_spec.rb
+++ b/spec/finders/ci/runners_finder_spec.rb
@@ -148,6 +148,14 @@ RSpec.describe Ci::RunnersFinder, feature_category: :runner_fleet do
described_class.new(current_user: admin, params: { tag_name: %w[tag1 tag2] }).execute
end
end
+
+ context 'by creator' do
+ it 'calls the corresponding scope on Ci::Runner' do
+ expect(Ci::Runner).to receive(:with_creator_id).with('1').and_call_original
+
+ described_class.new(current_user: admin, params: { creator_id: '1' }).execute
+ end
+ end
end
context 'sorting' do
@@ -608,6 +616,16 @@ RSpec.describe Ci::RunnersFinder, feature_category: :runner_fleet do
expect(subject).to match_array([runner_project_active, runner_project_inactive])
end
end
+
+ context 'by creator' do
+ let_it_be(:runner_creator_1) { create(:ci_runner, creator_id: '1') }
+
+ let(:extra_params) { { creator_id: '1' } }
+
+ it 'returns correct runners' do
+ is_expected.to contain_exactly(runner_creator_1)
+ end
+ end
end
end
diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js
deleted file mode 100644
index 1ca944dce12..00000000000
--- a/spec/frontend/users/profile/components/report_abuse_button_spec.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { GlButton } from '@gitlab/ui';
-import { createWrapper } from '@vue/test-utils';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import ReportAbuseButton from '~/users/profile/components/report_abuse_button.vue';
-import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
-
-describe('ReportAbuseButton', () => {
- let wrapper;
-
- const ACTION_PATH = '/abuse_reports/add_category';
- const USER_ID = 1;
- const REPORTED_FROM_URL = 'http://example.com';
-
- const createComponent = (props) => {
- wrapper = shallowMountExtended(ReportAbuseButton, {
- propsData: {
- ...props,
- },
- provide: {
- reportAbusePath: ACTION_PATH,
- reportedUserId: USER_ID,
- reportedFromUrl: REPORTED_FROM_URL,
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- const findReportAbuseButton = () => wrapper.findComponent(GlButton);
- const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
-
- it('renders report abuse button', () => {
- expect(findReportAbuseButton().exists()).toBe(true);
-
- expect(findReportAbuseButton().props()).toMatchObject({
- category: 'primary',
- icon: 'error',
- });
-
- expect(findReportAbuseButton().attributes('aria-label')).toBe(
- ReportAbuseButton.i18n.reportAbuse,
- );
- });
-
- it('renders abuse category selector with the drawer initially closed', () => {
- expect(findAbuseCategorySelector().exists()).toBe(true);
-
- expect(findAbuseCategorySelector().props('showDrawer')).toBe(false);
- });
-
- describe('when button is clicked', () => {
- beforeEach(async () => {
- await findReportAbuseButton().vm.$emit('click');
- });
-
- it('opens the abuse category selector', () => {
- expect(findAbuseCategorySelector().props('showDrawer')).toBe(true);
- });
-
- it('closes the abuse category selector', async () => {
- await findAbuseCategorySelector().vm.$emit('close-drawer');
-
- expect(findAbuseCategorySelector().props('showDrawer')).toBe(false);
- });
- });
-
- describe('when user hovers out of the button', () => {
- it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, () => {
- const rootWrapper = createWrapper(wrapper.vm.$root);
-
- findReportAbuseButton().vm.$emit('mouseout');
-
- expect(rootWrapper.emitted(BV_HIDE_TOOLTIP)).toHaveLength(1);
- });
- });
-});
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js b/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js
index a22763dcb45..d9b8f8aaeb5 100644
--- a/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js
@@ -2,10 +2,8 @@ import { graphqlQuery } from '../graphql';
export default (server) => {
server.post('/api/graphql', (schema, request) => {
- const batches = JSON.parse(request.requestBody);
+ const { query, variables } = JSON.parse(request.requestBody);
- return Promise.all(
- batches.map(({ query, variables }) => graphqlQuery(query, variables, schema)),
- );
+ return graphqlQuery(query, variables, schema);
});
};
diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
index c164393d605..88316f20c38 100644
--- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
@@ -85,7 +85,8 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d
type: :instance_type,
tag_list: ['active_runner'],
search: 'abc',
- sort: :contacted_asc
+ sort: :contacted_asc,
+ creator_id: 'gid://gitlab/User/1'
}
end
@@ -98,7 +99,8 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d
tag_name: ['active_runner'],
preload: false,
search: 'abc',
- sort: 'contacted_asc'
+ sort: 'contacted_asc',
+ creator_id: '1'
}
end
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
index 3e8d71ac673..bfde0a69f98 100644
--- a/spec/lib/gitlab/alert_management/payload/base_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do
context 'with multiple paths provided' do
let(:payload_class) do
Class.new(described_class) do
- attribute :test, paths: [['test'], %w(alt test)]
+ attribute :test, paths: [['test'], %w[alt test]]
end
end
@@ -204,8 +204,8 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do
end
context 'with too-long hosts array' do
- let(:hosts) { %w(abc def ghij) }
- let(:shortened_hosts) { %w(abc def ghi) }
+ let(:hosts) { %w[abc def ghij] }
+ let(:shortened_hosts) { %w[abc def ghi] }
before do
stub_const('::AlertManagement::Alert::HOSTS_MAX_LENGTH', 9)
@@ -215,15 +215,15 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do
it { is_expected.to eq(hosts: shortened_hosts, project_id: project.id) }
context 'with host cut off between elements' do
- let(:hosts) { %w(abcde fghij) }
- let(:shortened_hosts) { %w(abcde fghi) }
+ let(:hosts) { %w[abcde fghij] }
+ let(:shortened_hosts) { %w[abcde fghi] }
it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) }
end
context 'with nested hosts' do
let(:hosts) { ['abc', ['de', 'f'], 'g', 'hij'] } # rubocop:disable Style/WordArray
- let(:shortened_hosts) { %w(abc de f g hi) }
+ let(:shortened_hosts) { %w[abc de f g hi] }
it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) }
end
diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb
index 7d7952d5741..af8721739a0 100644
--- a/spec/lib/gitlab/asset_proxy_spec.rb
+++ b/spec/lib/gitlab/asset_proxy_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Gitlab::AssetProxy do
context 'when asset proxy is enabled' do
before do
- stub_asset_proxy_setting(allowlist: %w(gitlab.com *.mydomain.com))
+ stub_asset_proxy_setting(allowlist: %w[gitlab.com *.mydomain.com])
stub_asset_proxy_setting(
enabled: true,
url: 'https://assets.example.com',
diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
index c19d890a703..0208255d24d 100644
--- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe Gitlab::Auth::Ldap::AuthHash do
let(:attributes) do
{
- 'username' => %w(mail email),
+ 'username' => %w[mail email],
'name' => 'fullName'
}
end
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index 48039b58216..f97b16254e7 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -90,7 +90,7 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
end
it 'returns one provider' do
- expect(described_class.available_providers).to match_array(%w(ldapmain))
+ expect(described_class.available_providers).to match_array(%w[ldapmain])
end
end
@@ -552,15 +552,15 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
stub_ldap_config(
options: {
'attributes' => {
- 'username' => %w(sAMAccountName),
- 'email' => %w(userPrincipalName)
+ 'username' => %w[sAMAccountName],
+ 'email' => %w[userPrincipalName]
}
}
)
expect(config.attributes).to include({
- 'username' => %w(sAMAccountName),
- 'email' => %w(userPrincipalName),
+ 'username' => %w[sAMAccountName],
+ 'email' => %w[userPrincipalName],
'name' => 'cn'
})
end
diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb
index f8268bb1666..b5fd44d4aa9 100644
--- a/spec/lib/gitlab/auth/ldap/person_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/person_spec.rb
@@ -13,13 +13,13 @@ RSpec.describe Gitlab::Auth::Ldap::Person do
'uid' => 'uid',
'attributes' => {
'name' => 'cn',
- 'email' => %w(mail email userPrincipalName),
+ 'email' => %w[mail email userPrincipalName],
'username' => username_attribute
}
}
)
end
- let(:username_attribute) { %w(uid sAMAccountName userid) }
+ let(:username_attribute) { %w[uid sAMAccountName userid] }
describe '.normalize_dn' do
subject { described_class.normalize_dn(given) }
@@ -57,7 +57,7 @@ RSpec.describe Gitlab::Auth::Ldap::Person do
'attributes' => {
'name' => 'cn',
'email' => 'mail',
- 'username' => %w(uid mail),
+ 'username' => %w[uid mail],
'first_name' => ''
}
}
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 8a9182f6457..c137ca88589 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -369,7 +369,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
context "and at least one LDAP provider is defined" do
before do
- stub_ldap_config(providers: %w(ldapmain))
+ stub_ldap_config(providers: %w[ldapmain])
end
context "and a corresponding LDAP person" do
@@ -570,7 +570,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { 'johndoe@example.com' }
- allow(ldap_user).to receive(:email) { %w(johndoe@example.com john2@example.com) }
+ allow(ldap_user).to receive(:email) { %w[johndoe@example.com john2@example.com] }
allow(ldap_user).to receive(:dn) { dn }
end
@@ -605,7 +605,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
context "and at least one LDAP provider is defined" do
before do
- stub_ldap_config(providers: %w(ldapmain))
+ stub_ldap_config(providers: %w[ldapmain])
end
context "and a corresponding LDAP person" do
@@ -1055,7 +1055,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
context "update only requested info" do
before do
stub_omniauth_setting(sync_profile_from_provider: ['my-provider'])
- stub_omniauth_setting(sync_profile_attributes: %w(name location))
+ stub_omniauth_setting(sync_profile_attributes: %w[name location])
end
it "updates the user name" do
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index 5286e22abc9..e37b9b10834 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Saml::AuthHash do
include LoginHelpers
- let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } }
+ let(:raw_info_attr) { { 'groups' => %w[Developers Freelancers] } }
subject(:saml_auth_hash) { described_class.new(omniauth_auth_hash) }
let(:info_hash) do
@@ -23,12 +23,12 @@ RSpec.describe Gitlab::Auth::Saml::AuthHash do
end
before do
- stub_saml_group_config(%w(Developers Freelancers Designers))
+ stub_saml_group_config(%w[Developers Freelancers Designers])
end
describe '#groups' do
it 'returns array of groups' do
- expect(saml_auth_hash.groups).to eq(%w(Developers Freelancers))
+ expect(saml_auth_hash.groups).to eq(%w[Developers Freelancers])
end
context 'raw info hash attributes empty' do
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index a8a5d8ae5df..034d1a69a0b 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
let(:uid) { 'my-uid' }
let(:dn) { 'uid=user1,ou=people,dc=example' }
let(:provider) { 'saml' }
- let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers Designers) } }
+ let(:raw_info_attr) { { 'groups' => %w[Developers Freelancers Designers] } }
let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) }) }
let(:info_hash) do
{
@@ -47,12 +47,12 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'external groups' do
before do
- stub_saml_group_config(%w(Interns))
+ stub_saml_group_config(%w[Interns])
end
context 'are defined' do
it 'marks the user as external' do
- stub_saml_group_config(%w(Freelancers))
+ stub_saml_group_config(%w[Freelancers])
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -119,7 +119,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'external groups' do
context 'are defined' do
it 'marks the user as external' do
- stub_saml_group_config(%w(Freelancers))
+ stub_saml_group_config(%w[Freelancers])
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -128,7 +128,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'are defined but the user does not belong there' do
it 'does not mark the user as external' do
- stub_saml_group_config(%w(Interns))
+ stub_saml_group_config(%w[Interns])
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
@@ -151,7 +151,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'and at least one LDAP provider is defined' do
before do
- stub_ldap_config(providers: %w(ldapmain))
+ stub_ldap_config(providers: %w[ldapmain])
end
context 'and a corresponding LDAP person' do
@@ -160,7 +160,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
- allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) }
+ allow(ldap_user).to receive(:email) { %w[john@mail.com john2@example.com] }
allow(ldap_user).to receive(:dn) { dn }
allow(Gitlab::Auth::Ldap::Adapter).to receive(:new).and_return(adapter)
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user)
@@ -190,14 +190,14 @@ RSpec.describe Gitlab::Auth::Saml::User do
info: info_hash,
extra: {
raw_info: OneLogin::RubySaml::Attributes.new(
- { 'groups' => %w(Developers Freelancers Designers) }
+ { 'groups' => %w[Developers Freelancers Designers] }
)
}
}
end
let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes) }
- let(:uid_types) { %w(uid dn email) }
+ let(:uid_types) { %w[uid dn email] }
before do
create(:omniauth_user,
@@ -410,7 +410,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
let(:raw_info_attr) { {} }
it 'does not mark user as external' do
- stub_saml_group_config(%w(Freelancers))
+ stub_saml_group_config(%w[Freelancers])
expect(saml_user.find_user.external).to be_falsy
end
diff --git a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
index 781bf93dd85..7c09c69e5d1 100644
--- a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
@@ -9,15 +9,15 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
it 'defines generic instance with only some of the attributes set' do
generic_instance = described_class.generic_instance(
batch_table: 'projects', batch_column: 'id',
- job_arguments: %w(x y), connection: connection
+ job_arguments: %w[x y], connection: connection
)
expect(generic_instance.send(:batch_table)).to eq('projects')
expect(generic_instance.send(:batch_column)).to eq('id')
- expect(generic_instance.instance_variable_get(:@job_arguments)).to eq(%w(x y))
+ expect(generic_instance.instance_variable_get(:@job_arguments)).to eq(%w[x y])
expect(generic_instance.send(:connection)).to eq(connection)
- %i(start_id end_id sub_batch_size pause_ms).each do |attr|
+ %i[start_id end_id sub_batch_size pause_ms].each do |attr|
expect(generic_instance.send(attr)).to eq(0)
end
end
@@ -36,7 +36,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
batch_column: 'id',
sub_batch_size: 2,
pause_ms: 1000,
- job_arguments: %w(a b),
+ job_arguments: %w[a b],
connection: connection)
end
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
batch_column: 'id',
sub_batch_size: 2,
pause_ms: 1000,
- job_arguments: %w(a b),
+ job_arguments: %w[a b],
connection: connection)
end
@@ -129,7 +129,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
batch_column: 'id',
sub_batch_size: 2,
pause_ms: 1000,
- job_arguments: %w(a b),
+ job_arguments: %w[a b],
connection: connection)
end
diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
index 9c33100a0b3..06325f7a70b 100644
--- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers)
end
- let(:job_arguments) { %w(name name_convert_to_text) }
+ let(:job_arguments) { %w[name name_convert_to_text] }
let(:copy_job) do
described_class.new(start_id: 12,
end_id: 20,
@@ -82,7 +82,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
end
context 'columns with NULLs' do
- let(:job_arguments) { %w(name name_convert_to_text) }
+ let(:job_arguments) { %w[name name_convert_to_text] }
it 'copies all in range' do
expect { copy_job.perform }
diff --git a/spec/lib/gitlab/batch_worker_context_spec.rb b/spec/lib/gitlab/batch_worker_context_spec.rb
index 31641f7449e..a0a5bf0cba1 100644
--- a/spec/lib/gitlab/batch_worker_context_spec.rb
+++ b/spec/lib/gitlab/batch_worker_context_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::BatchWorkerContext do
subject(:batch_context) do
described_class.new(
- %w(hello world),
+ %w[hello world],
arguments_proc: -> (word) { word },
context_proc: -> (word) { { user: build_stubbed(:user, username: word) } }
)
@@ -13,13 +13,13 @@ RSpec.describe Gitlab::BatchWorkerContext do
describe "#arguments" do
it "returns all the expected arguments in arrays" do
- expect(batch_context.arguments).to eq([%w(hello), %w(world)])
+ expect(batch_context.arguments).to eq([%w[hello], %w[world]])
end
end
describe "#context_for" do
it "returns the correct application context for the arguments" do
- context = batch_context.context_for(%w(world))
+ context = batch_context.context_for(%w[world])
expect(context).to be_a(Gitlab::ApplicationContext)
expect(context.to_lazy_hash[:user].call).to eq("world")
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 517d557d665..d468483661a 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -220,7 +220,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
subject.execute
expect(subject.errors.count).to eq(1)
- expect(subject.errors.first.keys).to match_array(%i(type iid errors))
+ expect(subject.errors.first.keys).to match_array(%i[type iid errors])
end
end
diff --git a/spec/lib/gitlab/cache_spec.rb b/spec/lib/gitlab/cache_spec.rb
index 67c70a77880..92a5ea7bdfb 100644
--- a/spec/lib/gitlab/cache_spec.rb
+++ b/spec/lib/gitlab/cache_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::Cache, :request_store do
end
describe '.delete' do
- let(:key) { %w{a cache key} }
+ let(:key) { %w[a cache key] }
subject(:delete) { described_class.delete(key) }
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 30359a7170f..2990599f840 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -227,7 +227,7 @@ RSpec.describe Gitlab::Ci::Ansi2html do
text = "#{section_start}Some text#{section_end}"
class_name_start = section_start.gsub("\033[0K", '').gsub('<', '<')
class_name_end = section_end.gsub("\033[0K", '').gsub('<', '<')
- html = %{#{class_name_start}Some text#{class_name_end}}
+ html = %(#{class_name_start}Some text#{class_name_end})
expect(convert_html(text)).to eq(html)
end
@@ -238,9 +238,9 @@ RSpec.describe Gitlab::Ci::Ansi2html do
it 'prints light red' do
text = "#{section_start}\e[91mHello\e[0m\nLine 1\nLine 2\nLine 3\n#{section_end}"
- header = %{}
- line_break = %{}
- output_line = %{Line 1
Line 2
Line 3
}
+ header = %()
+ line_break = %()
+ output_line = %(Line 1
Line 2
Line 3
)
html = "#{section_start_html}#{header}#{line_break}#{output_line}#{section_end_html}"
expect(convert_html(text)).to eq(html)
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 567ffa68836..de3eb885056 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -283,8 +283,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
- parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) },
- { 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] },
+ parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w[monitoring app1] },
+ { 'PROVIDER' => ['gcp'], 'STACK' => %w[data] }] },
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index 1b8dfae692a..f84a78b4804 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Commands do
let(:entry) { described_class.new(config) }
context 'when entry config value is an array of strings' do
- let(:config) { %w(ls pwd) }
+ let(:config) { %w[ls pwd] }
describe '#value' do
it 'returns array of strings' do
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 3562706ff33..cff94a96c99 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -93,7 +93,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do
context 'when valid action is used' do
where(:action) do
- %w(start stop prepare verify access)
+ %w[start stop prepare verify access]
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index b37498ba10a..17c45ec4c2c 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
context 'when configuration is a hash' do
- let(:config) { { name: 'image:1.0', entrypoint: %w(/bin/sh run) } }
+ let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run] } }
describe '#value' do
it 'returns image hash' do
@@ -84,13 +84,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#entrypoint' do
it "returns image's entrypoint" do
- expect(entry.entrypoint).to eq %w(/bin/sh run)
+ expect(entry.entrypoint).to eq %w[/bin/sh run]
end
end
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
- let(:config) { { name: 'image:1.0', entrypoint: %w(/bin/sh run), ports: ports } }
+ let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run], ports: ports } }
let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 132e75a808b..44e2fdbac37 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -119,6 +119,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable, feature_category: :pipeli
end
end
+ context 'when script: and trigger: are used together' do
+ let(:config) do
+ {
+ script: 'echo',
+ trigger: 'test-group/test-project'
+ }
+ end
+
+ it 'returns is invalid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include(/these keys cannot be used together: script, trigger/)
+ end
+ end
+
context 'when only: is used with rules:' do
let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 5fac5298e8e..0370bcbccf5 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
context 'when top-level entries are defined' do
let(:hash) do
{
- before_script: %w(ls pwd),
+ before_script: %w[ls pwd],
image: 'image:1.0',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
VAR3: { value: 'val3', options: %w[val3 val4 val5], description: 'this is var 3 and some options' }
},
after_script: ['make clean'],
- stages: %w(build pages release),
+ stages: %w[build pages release],
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
spinach: { before_script: [], variables: {}, script: 'spinach' },
@@ -123,7 +123,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
expect(root.jobs_value[:rspec]).to eq(
{ name: :rspec,
script: %w[rspec ls],
- before_script: %w(ls pwd),
+ before_script: %w[ls pwd],
image: { name: 'image:1.0' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
@@ -162,7 +162,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success',
unprotect: false, fallback_keys: [] }],
- only: { refs: %w(branches tags) },
+ only: { refs: %w[branches tags] },
job_variables: { 'VAR' => { value: 'job' } },
root_variables_inheritance: true,
after_script: [],
@@ -176,14 +176,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
context 'when a mix of top-level and default entries is used' do
let(:hash) do
- { before_script: %w(ls pwd),
+ { before_script: %w[ls pwd],
after_script: ['make clean'],
default: {
image: 'image:1.0',
services: ['postgres:9.1', 'mysql:5.5']
},
variables: { VAR: 'root' },
- stages: %w(build pages),
+ stages: %w[build pages],
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } }
@@ -205,7 +205,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
expect(root.jobs_value).to eq(
rspec: { name: :rspec,
script: %w[rspec ls],
- before_script: %w(ls pwd),
+ before_script: %w[ls pwd],
image: { name: 'image:1.0' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index e36484bb0ae..1f935bebed5 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
context 'when configuration is a hash' do
let(:config) do
- { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
+ { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run] }
end
describe '#valid?' do
@@ -80,13 +80,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
describe '#command' do
it "returns service's command" do
- expect(entry.command).to eq %w(cmd run)
+ expect(entry.command).to eq %w[cmd run]
end
end
describe '#entrypoint' do
it "returns service's entrypoint" do
- expect(entry.entrypoint).to eq %w(/bin/sh run)
+ expect(entry.entrypoint).to eq %w[/bin/sh run]
end
end
@@ -99,7 +99,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) do
- { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run], ports: ports }
end
let(:entry) { described_class.new(config, with_image_ports: image_ports) }
@@ -198,7 +198,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
context 'when service has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) do
- { name: 'postgresql:9.5', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ { name: 'postgresql:9.5', command: %w[cmd run], entrypoint: %w[/bin/sh run], ports: ports }
end
it 'alias field is mandatory' do
@@ -209,7 +209,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
context 'when service does not have ports' do
let(:config) do
- { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
+ { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run] }
end
it 'alias field is optional' do
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index 69aa3bab77a..a395dfe886d 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -178,7 +178,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do
{
first: { script: 'my value', image: 'ubuntu' },
second: { image: 'alpine' },
- test: { extends: %w(first second) }
+ test: { extends: %w[first second] }
}
end
@@ -186,7 +186,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do
{
first: { script: 'my value', image: 'ubuntu' },
second: { image: 'alpine' },
- test: { extends: %w(first second), script: 'my value', image: 'alpine' }
+ test: { extends: %w[first second], script: 'my value', image: 'alpine' }
}
end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index 1415dbeb532..bcfab620bd9 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
end
context 'when location is not a string' do
- let(:location) { %w(some/file.txt other/file.txt) }
+ let(:location) { %w[some/file.txt other/file.txt] }
it { is_expected.to be_falsy }
end
diff --git a/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb
index 13999b2a9e5..640bed0d329 100644
--- a/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
let(:created_at) { 2.weeks.ago }
context "when parsing valid reports" do
- where(report_format: %i(secret_detection))
+ where(report_format: %i[secret_detection])
with_them do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
index ddd0de69d79..70d73a8095c 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
@@ -21,11 +21,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do
expect(command).to(
receive(:yaml_processor_result)
.and_return(
- double(included_templates: %w(Template-1 Template-2))
+ double(included_templates: %w[Template-1 Template-2])
)
)
- %w(Template-1 Template-2).each do |expected_template|
+ %w[Template-1 Template-2].each do |expected_template|
expect(Gitlab::UsageDataCounters::CiTemplateUniqueCounter).to(
receive(:track_unique_project_event)
.with(project: project, template: expected_template, config_source: pipeline.config_source, user: user)
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index ab223ae41fa..eb71cc0f0bc 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
context 'when left and right are equal' do
where(:left_value, :right_value) do
- [%w(string string)]
+ [%w[string string]]
end
with_them do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 54e569f424b..ef9b8f2b82f 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -395,7 +395,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co
end
context 'when root_variables_inheritance is an array' do
- let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+ let(:root_variables_inheritance) { %w[VAR1 VAR2 VAR3] }
it 'returns calculated yaml variables' do
expect(subject[:yaml_variables]).to match_array(
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
index ad8f1dc11f8..6d8b472a240 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
"type" => "error",
"typeCode" => 1,
"message" => "Anchor element found with a valid href attribute, but no link content has been supplied.",
- "context" => %{