Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-19 18:07:55 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-19 18:07:55 +0300
commit3c55affa6684311ca73bc4e3d3bfb17b7541f63b (patch)
treeb15020de36fa6b7c0b10bce78165eea79dc1455e
parent881435f2a3eeca1b5b544ad7c7510481b1773d1b (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/style/percent_literal_delimiters.yml65
-rw-r--r--app/assets/javascripts/lib/graphql.js14
-rw-r--r--app/assets/javascripts/pages/users/index.js2
-rw-r--r--app/assets/javascripts/users/profile/components/report_abuse_button.vue58
-rw-r--r--app/assets/javascripts/users/profile/index.js23
-rw-r--r--app/controllers/search_controller.rb11
-rw-r--r--app/finders/ci/runners_finder.rb6
-rw-r--r--app/graphql/resolvers/ci/runners_resolver.rb6
-rw-r--r--app/models/activity_pub.rb7
-rw-r--r--app/models/activity_pub/releases_subscription.rb22
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/note.rb6
-rw-r--r--app/services/import/validate_remote_git_endpoint_service.rb81
-rw-r--r--app/validators/json_schemas/activity_pub_follow_payload.json53
-rw-r--r--app/views/users/_cover_controls.html.haml2
-rw-r--r--app/views/users/_profile_basic_info.html.haml4
-rw-r--r--app/views/users/show.html.haml36
-rw-r--r--config/feature_flags/development/search_issues_hide_archived_projects.yml8
-rw-r--r--config/feature_flags/development/unbatch_graphql_queries.yml8
-rw-r--r--config/feature_flags/development/user_profile_overflow_menu_vue.yml8
-rw-r--r--config/initializers/database_query_analyzers.rb5
-rw-r--r--db/docs/activity_pub_releases_subscriptions.yml10
-rw-r--r--db/migrate/20230529182720_recreate_billable_index.rb2
-rw-r--r--db/migrate/20230529184716_recreated_activity_index.rb2
-rw-r--r--db/migrate/20230605043258_add_unconfirmed_created_at_index_to_users.rb2
-rw-r--r--db/migrate/20231017095738_create_activity_pub_releases_subscriptions.rb25
-rw-r--r--db/post_migrate/20220617123022_add_unique_index_on_projects_on_runners_token.rb2
-rw-r--r--db/post_migrate/20220617123034_add_unique_index_on_projects_on_runners_token_encrypted.rb2
-rw-r--r--db/post_migrate/20220920135356_tiebreak_user_type_index.rb2
-rw-r--r--db/post_migrate/20221018232820_add_temp_index_for_user_details_fields.rb2
-rw-r--r--db/post_migrate/20221221150123_update_billable_users_index.rb2
-rw-r--r--db/post_migrate/20230131184319_update_billable_users_index_for_service_accounts.rb2
-rw-r--r--db/post_migrate/20230303154314_add_user_type_migration_indexes.rb2
-rw-r--r--db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb2
-rw-r--r--db/post_migrate/20230619005223_change_unconfirmed_created_at_index_on_users.rb2
-rw-r--r--db/post_migrate/20230724150939_index_projects_on_namespace_id_and_repository_size_limit.rb2
-rw-r--r--db/post_migrate/20230728151058_add_auditor_index_to_users_table.rb2
-rw-r--r--db/post_migrate/20230913130629_index_org_id_on_projects.rb2
-rw-r--r--db/post_migrate/20231003142534_add_build_timeout_index.rb2
-rw-r--r--db/post_migrate/20231009105056_index_users_on_email_domain_and_id.rb2
-rw-r--r--db/post_migrate/20231017172156_add_index_on_projects_for_adjourned_deletion.rb2
-rw-r--r--db/schema_migrations/202310170957381
-rw-r--r--db/structure.sql36
-rw-r--r--doc/administration/logs/index.md4
-rw-r--r--doc/administration/settings/usage_statistics.md1
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/development/cells/index.md1
-rw-r--r--doc/user/group/saml_sso/troubleshooting.md18
-rw-r--r--doc/user/product_analytics/index.md4
-rw-r--r--doc/user/project/settings/project_access_tokens.md2
-rw-r--r--doc/user/report_abuse.md9
-rw-r--r--doc/user/search/index.md17
-rw-r--r--lib/gitlab/ci/config/entry/processable.rb2
-rw-r--r--lib/gitlab/config/entry/validators.rb3
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb47
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns.rb91
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions.rb74
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms.rb68
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb118
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references.rb64
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt.rb81
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets.rb97
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/type.rb16
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/search_results.rb4
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rw-r--r--rubocop/cop/migration/prevent_index_creation.rb4
-rw-r--r--spec/controllers/search_controller_spec.rb28
-rw-r--r--spec/factories/activity_pub/releases_subscriptions.rb26
-rw-r--r--spec/features/abuse_report_spec.rb116
-rw-r--r--spec/features/merge_request/user_reverts_merge_request_spec.rb15
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb3
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb7
-rw-r--r--spec/features/users/rss_spec.rb52
-rw-r--r--spec/features/users/show_spec.rb28
-rw-r--r--spec/finders/ci/runners_finder_spec.rb18
-rw-r--r--spec/frontend/users/profile/components/report_abuse_button_spec.js79
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/graphql.js6
-rw-r--r--spec/graphql/resolvers/ci/runners_resolver_spec.rb6
-rw-r--r--spec/lib/gitlab/alert_management/payload/base_spec.rb12
-rw-r--r--spec/lib/gitlab/asset_proxy_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/ldap/person_spec.rb6
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb8
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb6
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb20
-rw-r--r--spec/lib/gitlab/background_migration/batched_migration_job_spec.rb12
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb4
-rw-r--r--spec/lib/gitlab/batch_worker_context_spec.rb6
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb42
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/dag_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb54
-rw-r--r--spec/lib/gitlab/config/entry/factory_spec.rb12
-rw-r--r--spec/lib/gitlab/config/entry/validators_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb4
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb4
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_job_spec.rb2
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb4
-rw-r--r--spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb40
-rw-r--r--spec/lib/gitlab/database/postgres_index_spec.rb4
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb88
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb97
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb76
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb68
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb41
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb361
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb94
-rw-r--r--spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb84
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb2
-rw-r--r--spec/lib/gitlab/database/transaction/observer_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/base_linker_spec.rb4
-rw-r--r--spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb6
-rw-r--r--spec/lib/gitlab/search_results_spec.rb2
-rw-r--r--spec/models/activity_pub/releases_subscription_spec.rb79
-rw-r--r--spec/models/ci/runner_spec.rb13
-rw-r--r--spec/models/concerns/reset_on_column_errors_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb43
-rw-r--r--spec/requests/api/projects_spec.rb5
-rw-r--r--spec/rubocop/cop/migration/prevent_index_creation_spec.rb2
-rw-r--r--spec/services/import/validate_remote_git_endpoint_service_spec.rb43
-rw-r--r--spec/services/notes/create_service_spec.rb39
-rw-r--r--spec/support/helpers/prevent_set_operator_mismatch_helper.rb16
-rw-r--r--spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb2
-rw-r--r--yarn.lock14
167 files changed, 2448 insertions, 867 deletions
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 @@
-<script>
-import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-
-import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
-
-export default {
- name: 'ReportAbuseButton',
- components: {
- GlButton,
- AbuseCategorySelector,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- inject: ['reportedUserId', 'reportedFromUrl'],
- i18n: {
- reportAbuse: s__('ReportAbuse|Report abuse to administrator'),
- },
- data() {
- return {
- open: false,
- };
- },
- computed: {
- buttonTooltipText() {
- return this.$options.i18n.reportAbuse;
- },
- },
- methods: {
- toggleDrawer(open) {
- this.open = open;
- },
- hideTooltips() {
- this.$root.$emit(BV_HIDE_TOOLTIP);
- },
- },
-};
-</script>
-<template>
- <span>
- <gl-button
- v-gl-tooltip="buttonTooltipText"
- category="primary"
- :aria-label="buttonTooltipText"
- icon="error"
- @click="toggleDrawer(true)"
- @mouseout="hideTooltips"
- />
- <abuse-category-selector
- :reported-user-id="reportedUserId"
- :reported-from-url="reportedFromUrl"
- :show-drawer="open"
- @close-drawer="toggleDrawer(false)"
- />
- </span>
-</template>
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
@@ -40,10 +45,6 @@ class SearchController < ApplicationController
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)
-
- 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
- )
- 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
+ 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
+
+ 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
- [response, response_body]
+ if result == :success
+ ServiceResponse.success
+ else
+ ServiceResponse.error(message: "#{uri} is not a valid HTTP Git repository")
+ end
end
+ private
+
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
- 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
- )
- 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'
+ def status_code_is_valid(fragment)
+ fragment.http_response.code == '200'
end
- def content_type_is_valid?(response)
- response.http_response['content-type'] == EXPECTED_CONTENT_TYPE
+ def content_type_is_valid(fragment)
+ fragment.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 |
| ---- | ---- | ----------- |
| <a id="queryrunnersactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| <a id="queryrunnerscreatorid"></a>`creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| <a id="queryrunnerspaused"></a>`paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| <a id="queryrunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="queryrunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
@@ -18595,6 +18596,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="grouprunnersactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| <a id="grouprunnerscreatorid"></a>`creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| <a id="grouprunnersmembership"></a>`membership` | [`CiRunnerMembershipFilter`](#cirunnermembershipfilter) | Control which runners to include in the results. |
| <a id="grouprunnerspaused"></a>`paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| <a id="grouprunnerssearch"></a>`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 |
| ---- | ---- | ----------- |
| <a id="projectrunnersactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| <a id="projectrunnerscreatorid"></a>`creatorId` | [`UserID`](#userid) | Filter runners by creator ID. |
| <a id="projectrunnerspaused"></a>`paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| <a id="projectrunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="projectrunnerssort"></a>`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
+<Attribute Name="email">
+ <AttributeValue>user@domain.com‹/AttributeValue>
+</Attribute>
+```
+
+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
+<Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/email">
+ <AttributeValue>user@domain.com‹/AttributeValue>
+</Attribute>
+```
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) }
- before do
- stub_feature_flags(user_profile_overflow_menu_vue: false)
- visit user_path(abusive_user)
- 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)
+ it_behaves_like 'reports the user with an abuse category'
- 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'
+ find_by_testid('base-dropdown-toggle').click
+ 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'
- fill_and_submit_abuse_category_form("They're being offensive or abusive.")
- fill_and_submit_report_abuse_form
+ visit user_path(abusive_user)
- expect(page).to have_content 'Thank you for your report'
- end
+ 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
- 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'
+ end
- expect(page).to have_content 'Thank you for your report'
+ it 'allows multiple users to report the same user' do
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- gitlab_sign_out
- gitlab_sign_in(reporter2)
+ expect(page).to have_content 'Thank you for your report'
- visit user_path(abusive_user)
+ gitlab_sign_out
+ gitlab_sign_in(reporter2)
- 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'
- end
+ find_by_testid('base-dropdown-toggle').click
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- it_behaves_like 'cancel report'
+ 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' do
- find('[data-testid="base-dropdown-toggle"').click
+ it 'has an RSS without a feed token', :js 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
-
- expect(page).not_to have_content("User ID: #{user.id}")
- end
-
- 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 copy user id action in the dropdown', :js do
+ subject
- it 'shows user id' do
- subject
+ find('[data-testid="base-dropdown-toggle"').click
- 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('<', '&lt;')
class_name_end = section_end.gsub("\033[0K", '').gsub('<', '&lt;')
- html = %{<span>#{class_name_start}Some text#{class_name_end}</span>}
+ html = %(<span>#{class_name_start}Some text#{class_name_end}</span>)
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 = %{<span class="term-fg-l-red section section-header js-s-#{class_name(section_name)}">Hello</span>}
- line_break = %{<span class="section section-header js-s-#{class_name(section_name)}"><br/></span>}
- output_line = %{<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>}
+ header = %(<span class="term-fg-l-red section section-header js-s-#{class_name(section_name)}">Hello</span>)
+ line_break = %(<span class="section section-header js-s-#{class_name(section_name)}"><br/></span>)
+ output_line = %(<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>)
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" => %{<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>},
+ "context" => %(<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>),
"selector" => "#main-nav > div:nth-child(1) > a",
"runner" => "htmlcs",
"runnerExtras" => {}
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
"type" => "error",
"typeCode" => 1,
"message" => "This element has insufficient contrast at this conformance level.",
- "context" => %{<a href="/stages-devops-lifecycle/" class="main-nav-link">Product</a>},
+ "context" => %(<a href="/stages-devops-lifecycle/" class="main-nav-link">Product</a>),
"selector" => "#main-nav > div:nth-child(2) > ul > li:nth-child(1) > a",
"runner" => "htmlcs",
"runnerExtras" => {}
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
index af6844491ca..dff59474746 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do
"type": "error",
"typeCode": 1,
"message": "Anchor element found with a valid href attribute, but no link content has been supplied.",
- "context": %{<a href="/customers/worldline"><svg viewBox="0 0 509 89" xmln...</a>},
+ "context": %(<a href="/customers/worldline"><svg viewBox="0 0 509 89" xmln...</a>),
"selector": "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(17)",
"runner": "htmlcs",
"runnerExtras": {}
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do
"type": "error",
"typeCode": 1,
"message": "Anchor element found with a valid href attribute, but no link content has been supplied.",
- "context": %{<a href="/customers/equinix"><svg xmlns="http://www.w3.org/...</a>},
+ "context": %(<a href="/customers/equinix"><svg xmlns="http://www.w3.org/...</a>),
"selector": "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(18)",
"runner": "htmlcs",
"runnerExtras": {}
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index 05f6a8a8cb6..46ab0802200 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -192,7 +192,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
end
context 'when there are multiple test cases' do
- let(:status_ordered) { %w(error failed success skipped) }
+ let(:status_ordered) { %w[error failed success skipped] }
before do
test_suite.add_test_case(test_case_success)
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index cbf0976c976..32b95c433b2 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -43,28 +43,28 @@ RSpec.describe Gitlab::Ci::Status::Composite, feature_category: :continuous_inte
context 'allow_failure: false' do
where(:build_statuses, :dag, :result, :has_warnings) do
- %i(skipped) | false | 'skipped' | false
- %i(skipped success) | false | 'success' | false
- %i(skipped success) | true | 'skipped' | false
- %i(created) | false | 'created' | false
- %i(preparing) | false | 'preparing' | false
- %i(canceled success skipped) | false | 'canceled' | false
- %i(canceled success skipped) | true | 'skipped' | false
- %i(pending created skipped) | false | 'pending' | false
- %i(pending created skipped success) | false | 'running' | false
- %i(running created skipped success) | false | 'running' | false
- %i(pending created skipped) | true | 'skipped' | false
- %i(pending created skipped success) | true | 'skipped' | false
- %i(running created skipped success) | true | 'skipped' | false
- %i(success waiting_for_resource) | false | 'waiting_for_resource' | false
- %i(success manual) | false | 'manual' | false
- %i(success scheduled) | false | 'scheduled' | false
- %i(created preparing) | false | 'preparing' | false
- %i(created success pending) | false | 'running' | false
- %i(skipped success failed) | false | 'failed' | false
- %i(skipped success failed) | true | 'skipped' | false
- %i(success manual) | true | 'manual' | false
- %i(success failed created) | true | 'running' | false
+ %i[skipped] | false | 'skipped' | false
+ %i[skipped success] | false | 'success' | false
+ %i[skipped success] | true | 'skipped' | false
+ %i[created] | false | 'created' | false
+ %i[preparing] | false | 'preparing' | false
+ %i[canceled success skipped] | false | 'canceled' | false
+ %i[canceled success skipped] | true | 'skipped' | false
+ %i[pending created skipped] | false | 'pending' | false
+ %i[pending created skipped success] | false | 'running' | false
+ %i[running created skipped success] | false | 'running' | false
+ %i[pending created skipped] | true | 'skipped' | false
+ %i[pending created skipped success] | true | 'skipped' | false
+ %i[running created skipped success] | true | 'skipped' | false
+ %i[success waiting_for_resource] | false | 'waiting_for_resource' | false
+ %i[success manual] | false | 'manual' | false
+ %i[success scheduled] | false | 'scheduled' | false
+ %i[created preparing] | false | 'preparing' | false
+ %i[created success pending] | false | 'running' | false
+ %i[skipped success failed] | false | 'failed' | false
+ %i[skipped success failed] | true | 'skipped' | false
+ %i[success manual] | true | 'manual' | false
+ %i[success failed created] | true | 'running' | false
end
with_them do
@@ -78,13 +78,13 @@ RSpec.describe Gitlab::Ci::Status::Composite, feature_category: :continuous_inte
context 'allow_failure: true' do
where(:build_statuses, :dag, :result, :has_warnings) do
- %i(manual) | false | 'skipped' | false
- %i(skipped failed) | false | 'success' | true
- %i(skipped failed) | true | 'skipped' | true
- %i(success manual) | true | 'skipped' | false
- %i(success manual) | false | 'success' | false
- %i(created failed) | false | 'created' | true
- %i(preparing manual) | false | 'preparing' | false
+ %i[manual] | false | 'skipped' | false
+ %i[skipped failed] | false | 'success' | true
+ %i[skipped failed] | true | 'skipped' | true
+ %i[success manual] | true | 'skipped' | false
+ %i[success manual] | false | 'success' | false
+ %i[created failed] | false | 'created' | true
+ %i[preparing manual] | false | 'preparing' | false
end
with_them do
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 34e430202c9..98fefea7bdf 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous
end
context 'when stage has a core status' do
- (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - %w[manual skipped scheduled]).each do |core_status|
context "when core status is #{core_status}" do
let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index acb296082b8..dc9999ab9e4 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
context 'with no cluster or agent' do
it 'does not create any kubernetes deployment jobs' do
- expect(build_names).to eq %w(placeholder)
+ expect(build_names).to eq %w[placeholder]
end
end
@@ -68,7 +68,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
end
it 'does not create any kubernetes deployment jobs' do
- expect(build_names).to eq %w(placeholder)
+ expect(build_names).to eq %w[placeholder]
end
end
@@ -81,7 +81,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
it 'when CI_DEPLOY_FREEZE is present' do
create(:ci_variable, project: project, key: 'CI_DEPLOY_FREEZE', value: 'true')
- expect(build_names).to eq %w(placeholder)
+ expect(build_names).to eq %w[placeholder]
end
it 'when CANARY_ENABLED' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
index 2b9213ea921..86bc9259789 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml', feature_category: :continuo
it 'creates a pipeline with the expected jobs' do
expect(pipeline).to be_merge_request_event
expect(pipeline.errors.full_messages).to be_empty
- expect(build_names).to match_array(%w(kics-iac-sast))
+ expect(build_names).to match_array(%w[kics-iac-sast])
end
end
end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index 09ca2678de5..7471dc58e44 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -94,7 +94,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do
project.repository.create_branch(pipeline_branch, default_branch)
end
- %w(review_ecs review_fargate).each do |job|
+ %w[review_ecs review_fargate].each do |job|
it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
let(:job_name) { job }
end
@@ -142,7 +142,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do
context 'when the project has no active cluster' do
it 'only creates a build and a test stage' do
- expect(pipeline.stages_names).to eq(%w(build test))
+ expect(pipeline.stages_names).to eq(%w[build test])
end
it_behaves_like 'no Kubernetes deployment job'
@@ -273,25 +273,25 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do
using RSpec::Parameterized::TableSyntax
where(:case_name, :files, :variables, :include_build_names, :not_include_build_names) do
- 'No match' | { 'README.md' => '' } | {} | %w() | %w(build test)
- 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
- 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
- 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
- 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w()
- 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
- 'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
- 'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
- 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w(build test) | %w()
- 'Gradle' | { 'gradlew' => '' } | {} | %w(build test) | %w()
- 'Java' | { 'pom.xml' => '' } | {} | %w(build test) | %w()
- 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w(build test) | %w()
- 'NodeJS' | { 'package.json' => '' } | {} | %w(build test) | %w()
- 'PHP' | { 'composer.json' => '' } | {} | %w(build test) | %w()
- 'Play' | { 'conf/application.conf' => '' } | {} | %w(build test) | %w()
- 'Python' | { 'Pipfile' => '' } | {} | %w(build test) | %w()
- 'Ruby' | { 'Gemfile' => '' } | {} | %w(build test) | %w()
- 'Scala' | { 'build.sbt' => '' } | {} | %w(build test) | %w()
- 'Static' | { '.static' => '' } | {} | %w(build test) | %w()
+ 'No match' | { 'README.md' => '' } | {} | %w[] | %w[build test]
+ 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w[build test] | %w[]
+ 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w[build test] | %w[]
+ 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w[] | %w[build test]
+ 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w[build test] | %w[]
+ 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w[build test] | %w[]
+ 'Clojure' | { 'project.clj' => '' } | {} | %w[build test] | %w[]
+ 'Go modules' | { 'go.mod' => '' } | {} | %w[build test] | %w[]
+ 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w[build test] | %w[]
+ 'Gradle' | { 'gradlew' => '' } | {} | %w[build test] | %w[]
+ 'Java' | { 'pom.xml' => '' } | {} | %w[build test] | %w[]
+ 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w[build test] | %w[]
+ 'NodeJS' | { 'package.json' => '' } | {} | %w[build test] | %w[]
+ 'PHP' | { 'composer.json' => '' } | {} | %w[build test] | %w[]
+ 'Play' | { 'conf/application.conf' => '' } | {} | %w[build test] | %w[]
+ 'Python' | { 'Pipfile' => '' } | {} | %w[build test] | %w[]
+ 'Ruby' | { 'Gemfile' => '' } | {} | %w[build test] | %w[]
+ 'Scala' | { 'build.sbt' => '' } | {} | %w[build test] | %w[]
+ 'Static' | { '.static' => '' } | {} | %w[build test] | %w[]
end
with_them do
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index d96c8f1bd0c..aa612899f4b 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -123,11 +123,11 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre
},
"simple variable reference": {
variable: { key: 'VAR', value: 'something_$VAR2' },
- expected_depends_on: %w(VAR2)
+ expected_depends_on: %w[VAR2]
},
"complex expansion": {
variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3' },
- expected_depends_on: %w(VAR2 VAR3)
+ expected_depends_on: %w[VAR2 VAR3]
},
"complex expansion in raw variable": {
variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3', raw: true },
@@ -135,7 +135,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre
},
"complex expansions for Windows": {
variable: { key: 'variable3', value: 'key%variable%%variable2%' },
- expected_depends_on: %w(variable variable2)
+ expected_depends_on: %w[variable variable2]
}
}
end
@@ -282,7 +282,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre
it '#depends_on contains names of dependencies' do
runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3')
- expect(runner_variable.depends_on).to eq(%w(CI_VAR_2 CI_VAR_3))
+ expect(runner_variable.depends_on).to eq(%w[CI_VAR_2 CI_VAR_3])
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb
index 082febacbd7..d688937f997 100644
--- a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb
@@ -10,17 +10,17 @@ RSpec.describe Gitlab::Ci::YamlProcessor::Dag do
context 'when it is a regular pipeline' do
let(:nodes) do
- { 'job_c' => %w(job_b job_d), 'job_d' => %w(job_a), 'job_b' => %w(job_a), 'job_a' => %w() }
+ { 'job_c' => %w[job_b job_d], 'job_d' => %w[job_a], 'job_b' => %w[job_a], 'job_a' => %w[] }
end
it 'returns ordered jobs' do
- expect(result).to eq(%w(job_a job_b job_d job_c))
+ expect(result).to eq(%w[job_a job_b job_d job_c])
end
end
context 'when there is a circular dependency' do
let(:nodes) do
- { 'job_a' => %w(job_c), 'job_b' => %w(job_a), 'job_c' => %w(job_b) }
+ { 'job_a' => %w[job_c], 'job_b' => %w[job_a], 'job_c' => %w[job_b] }
end
it 'raises TSort::Cyclic' do
@@ -30,11 +30,11 @@ RSpec.describe Gitlab::Ci::YamlProcessor::Dag do
context 'when there are some missing jobs' do
let(:nodes) do
- { 'job_a' => %w(job_d job_f), 'job_b' => %w(job_a job_c job_e) }
+ { 'job_a' => %w[job_d job_f], 'job_b' => %w[job_a job_c job_e] }
end
it 'ignores the missing ones and returns in a valid order' do
- expect(result).to eq(%w(job_d job_f job_a job_c job_e job_b))
+ expect(result).to eq(%w[job_d job_f job_a job_c job_e job_b])
end
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 81bc8c7ab9a..b556b5da44f 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1510,7 +1510,7 @@ module Gitlab
it 'correctly extends rspec job' do
expect(config_processor.builds).to be_one
- expect(subject.dig(:options, :script)).to eq %w(test)
+ expect(subject.dig(:options, :script)).to eq %w[test]
expect(subject.dig(:options, :image, :name)).to eq 'ruby:alpine'
end
end
@@ -1595,7 +1595,7 @@ module Gitlab
it 'correctly extends rspec job' do
expect(config_processor.builds).to be_one
expect(subject.dig(:options, :before_script)).to eq ["bundle install"]
- expect(subject.dig(:options, :script)).to eq %w(rspec)
+ expect(subject.dig(:options, :script)).to eq %w[rspec]
expect(subject.dig(:options, :image, :name)).to eq 'image:test'
expect(subject.dig(:when)).to eq 'always'
end
@@ -2386,7 +2386,7 @@ module Gitlab
end
context 'dependencies to builds' do
- let(:dependencies) { %w(build1 build2) }
+ let(:dependencies) { %w[build1 build2] }
it { is_expected.to be_valid }
end
@@ -2457,7 +2457,7 @@ module Gitlab
end
context 'needs a job from the same stage' do
- let(:needs) { %w(test2) }
+ let(:needs) { %w[test2] }
it 'creates jobs with valid specifications' do
expect(subject.builds.size).to eq(7)
@@ -2494,7 +2494,7 @@ module Gitlab
end
context 'needs two builds' do
- let(:needs) { %w(build1 build2) }
+ let(:needs) { %w[build1 build2] }
it "does create jobs with valid specification" do
expect(subject.builds.size).to eq(7)
@@ -2578,7 +2578,7 @@ module Gitlab
end
context 'needs parallel job' do
- let(:needs) { %w(parallel) }
+ let(:needs) { %w[parallel] }
it "does create jobs with valid specification" do
expect(subject.builds.size).to eq(7)
@@ -2707,7 +2707,7 @@ module Gitlab
context 'duplicate needs' do
context 'when needs are specified in an array' do
- let(:needs) { %w(build1 build1) }
+ let(:needs) { %w[build1 build1] }
it_behaves_like 'returns errors', 'test1 has the following needs duplicated: build1.'
end
@@ -2736,8 +2736,8 @@ module Gitlab
end
context 'needs and dependencies that are mismatching' do
- let(:needs) { %w(build1) }
- let(:dependencies) { %w(build2) }
+ let(:needs) { %w[build1] }
+ let(:dependencies) { %w[build2] }
it_behaves_like 'returns errors', 'jobs:test1 dependencies the build2 should be part of needs'
end
@@ -2750,13 +2750,13 @@ module Gitlab
]
end
- let(:dependencies) { %w(build3) }
+ let(:dependencies) { %w[build3] }
it_behaves_like 'returns errors', 'jobs:test1 dependencies the build3 should be part of needs'
end
context 'needs with an array type and dependency with a string type' do
- let(:needs) { %w(build1) }
+ let(:needs) { %w[build1] }
let(:dependencies) { 'deploy' }
it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings'
@@ -2764,7 +2764,7 @@ module Gitlab
context 'needs with a string type and dependency with an array type' do
let(:needs) { 'build1' }
- let(:dependencies) { %w(deploy) }
+ let(:dependencies) { %w[deploy] }
it_behaves_like 'returns errors', 'jobs:test1:needs config can only be a hash or an array'
end
@@ -3252,7 +3252,7 @@ module Gitlab
end
context 'returns errors if job stage is not a defined stage' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", stage: "acceptance" } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", stage: "acceptance" } }) }
it_behaves_like 'returns errors', 'rspec job: chosen stage does not exist; available stages are .pre, build, test, .post'
end
@@ -3288,37 +3288,37 @@ module Gitlab
end
context 'returns errors if job artifacts:name is not an a string' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { name: 1 } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts name should be a string'
end
context 'returns errors if job artifacts:when is not an a predefined value' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { when: 1 } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts when should be one of: on_success, on_failure, always'
end
context 'returns errors if job artifacts:expire_in is not an a string' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { expire_in: 1 } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration'
end
context 'returns errors if job artifacts:expire_in is not an a valid duration' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration'
end
context 'returns errors if job artifacts:untracked is not an array of strings' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { untracked: "string" } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts untracked should be a boolean value'
end
context 'returns errors if job artifacts:paths is not an array of strings' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { paths: "string" } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:artifacts paths should be an array of strings'
end
@@ -3342,49 +3342,49 @@ module Gitlab
end
context 'returns errors if job cache:key is not an a string' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: 1 } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: 1 } } }) }
it_behaves_like 'returns errors', "jobs:rspec:cache:key should be a hash, a string or a symbol"
end
context 'returns errors if job cache:key:files is not an array of strings' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { files: [1] } } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { files: [1] } } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config should be an array of strings'
end
context 'returns errors if job cache:key:files is an empty array' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { files: [] } } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { files: [] } } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config requires at least 1 item'
end
context 'returns errors if job defines only cache:key:prefix' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:cache:key config missing required keys: files'
end
context 'returns errors if job cache:key:prefix is not an a string' do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) }
it_behaves_like 'returns errors', 'jobs:rspec:cache:key:prefix config should be a string or symbol'
end
context "returns errors if job cache:untracked is not an array of strings" do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { untracked: "string" } } }) }
it_behaves_like 'returns errors', "jobs:rspec:cache:untracked config should be a boolean value"
end
context "returns errors if job cache:paths is not an array of strings" do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { paths: "string" } } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { paths: "string" } } }) }
it_behaves_like 'returns errors', "jobs:rspec:cache:paths config should be an array of strings"
end
context "returns errors if job dependencies is not an array of strings" do
- let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", dependencies: "string" } }) }
+ let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", dependencies: "string" } }) }
it_behaves_like 'returns errors', "jobs:rspec dependencies should be an array of strings"
end
diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb
index be4dfd31651..bbbba0cf7cd 100644
--- a/spec/lib/gitlab/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/config/entry/factory_spec.rb
@@ -21,16 +21,16 @@ RSpec.describe Gitlab::Config::Entry::Factory do
context 'when setting a concrete value' do
it 'creates entry with valid value' do
entry = factory
- .value(%w(ls pwd))
+ .value(%w[ls pwd])
.create!
- expect(entry.value).to eq %w(ls pwd)
+ expect(entry.value).to eq %w[ls pwd]
end
context 'when setting description' do
before do
factory
- .value(%w(ls pwd))
+ .value(%w[ls pwd])
.with(description: 'test description')
end
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do
it 'creates entry with description' do
entry = factory.create!
- expect(entry.value).to eq %w(ls pwd)
+ expect(entry.value).to eq %w[ls pwd]
expect(entry.description).to eq 'test description'
end
end
@@ -49,7 +49,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do
context 'when setting inherit' do
before do
factory
- .value(%w(ls pwd))
+ .value(%w[ls pwd])
.with(inherit: true)
end
@@ -61,7 +61,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do
context 'when setting key' do
it 'creates entry with custom key' do
entry = factory
- .value(%w(ls pwd))
+ .value(%w[ls pwd])
.with(key: 'test key')
.create!
diff --git a/spec/lib/gitlab/config/entry/validators_spec.rb b/spec/lib/gitlab/config/entry/validators_spec.rb
index 6fa9f9d0767..e13c09f97ca 100644
--- a/spec/lib/gitlab/config/entry/validators_spec.rb
+++ b/spec/lib/gitlab/config/entry/validators_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::Config::Entry::Validators, feature_category: :pipeline_co
expect(instance.valid?).to be(valid_result)
unless valid_result
- expect(instance.errors.messages_for(:config)).to include /please use only one of the following keys: foo, bar/
+ expect(instance.errors.messages_for(:config)).to include(/these keys cannot be used together: foo, bar/)
end
end
end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 6ea8e6c6706..49252a6537c 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe Gitlab::Conflict::File do
it 'returns a file containing only the chosen parts of the resolved sections' do
expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first))
- .to eq(%w(both new both old both new both))
+ .to eq(%w[both new both old both new both])
end
end
@@ -193,7 +193,7 @@ RSpec.describe Gitlab::Conflict::File do
it 'sets conflict to true for sections with only changed lines' do
conflict_file.sections.select { |section| section[:conflict] }.each do |section|
section[:lines].each do |line|
- expect(line.type).to be_in(%w(new old))
+ expect(line.type).to be_in(%w[new old])
end
end
end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 66890315ee8..7afd16f53e5 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do
it { expect(data[:commit][:id]).to eq(ci_build.pipeline.id) }
it { expect(data[:runner][:id]).to eq(ci_build.runner.id) }
- it { expect(data[:runner][:tags]).to match_array(%w(tag1 tag2)) }
+ it { expect(data[:runner][:tags]).to match_array(%w[tag1 tag2]) }
it { expect(data[:runner][:description]).to eq(ci_build.runner.description) }
it { expect(data[:runner][:runner_type]).to eq(ci_build.runner.runner_type) }
it { expect(data[:runner][:is_shared]).to eq(ci_build.runner.instance_type?) }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 351872ffbc5..ad7cd2dc736 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline, feature_category: :continuous_inte
end
context 'build with runner' do
- let_it_be(:tag_names) { %w(tag-1 tag-2) }
+ let_it_be(:tag_names) { %w[tag-1 tag-2] }
let_it_be(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n) }) }
let_it_be(:build) { create(:ci_build, pipeline: pipeline, runner: ci_runner) }
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index a3dd4e49e83..02dc596c5eb 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe Gitlab::DataBuilder::Push do
it 'returns commit hook data' do
expect(subject[:project]).to eq(project.hook_attrs)
- expect(subject[:commits].first.keys).to include(*%i(added removed modified))
+ expect(subject[:commits].first.keys).to include(*%i[added removed modified])
end
end
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::DataBuilder::Push do
it 'returns commit hook data without include deltas' do
expect(subject[:project]).to eq(project.hook_attrs)
- expect(subject[:commits].first.keys).not_to include(*%i(added removed modified))
+ expect(subject[:commits].first.keys).not_to include(*%i[added removed modified])
end
end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
index d9b81a2be30..e1d1674d05c 100644
--- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
describe 'state machine' do
let_it_be(:job) { create(:batched_background_migration_job, :failed) }
- it { expect(described_class.state_machine.states.map(&:name)).to eql(%i(pending running failed succeeded)) }
+ it { expect(described_class.state_machine.states.map(&:name)).to eql(%i[pending running failed succeeded]) }
context 'when a job is running' do
it 'logs the transition' do
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb
index 59f4f40c0ef..7cf7be8ffc2 100644
--- a/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJobTransitionLog, t
it { is_expected.to validate_presence_of(:batched_job) }
it { is_expected.to validate_length_of(:exception_class).is_at_most(100) }
it { is_expected.to validate_length_of(:exception_message).is_at_most(1000) }
- it { is_expected.to define_enum_for(:previous_status).with_values(%i(pending running failed succeeded)).with_prefix }
- it { is_expected.to define_enum_for(:next_status).with_values(%i(pending running failed succeeded)).with_prefix }
+ it { is_expected.to define_enum_for(:previous_status).with_values(%i[pending running failed succeeded]).with_prefix }
+ it { is_expected.to define_enum_for(:next_status).with_values(%i[pending running failed succeeded]).with_prefix }
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb b/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb
index e11ffe53c61..fb3da38a7be 100644
--- a/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings do
it 'raises an error when some columns already exist' do
expect do
migration.add_cascading_namespace_setting(:cascading_setting, :integer)
- end.to raise_error %r/Existing columns: namespace_settings.cascading_setting, application_settings.lock_cascading_setting/
+ end.to raise_error %r{Existing columns: namespace_settings.cascading_setting, application_settings.lock_cascading_setting}
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index dd51cca688c..8bf05f56b3f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
it 'cannot add unacceptable column names' do
expect do
model.add_timestamps_with_timezone(:foo, columns: [:bar])
- end.to raise_error %r/Illegal timestamp column name/
+ end.to raise_error %r{Illegal timestamp column name}
end
end
@@ -1753,8 +1753,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
describe '#indexes_for' do
it 'returns the indexes for a column' do
- idx1 = double(:idx, columns: %w(project_id))
- idx2 = double(:idx, columns: %w(user_id))
+ idx1 = double(:idx, columns: %w[project_id])
+ idx2 = double(:idx, columns: %w[user_id])
allow(model).to receive(:indexes).with('table').and_return([idx1, idx2])
@@ -1777,7 +1777,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'when index name is too long' do
it 'does not fail' do
index = double(:index,
- columns: %w(uuid),
+ columns: %w[uuid],
name: 'index_vuln_findings_on_uuid_including_vuln_id_1',
using: nil,
where: nil,
@@ -1791,7 +1791,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:vulnerability_occurrences,
- %w(tmp_undo_cleanup_column_8cbf300838),
+ %w[tmp_undo_cleanup_column_8cbf300838],
{
unique: true,
name: 'idx_copy_191a1af1a0',
@@ -1806,7 +1806,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'using a regular index using a single column' do
it 'copies the index' do
index = double(:index,
- columns: %w(project_id),
+ columns: %w[project_id],
name: 'index_on_issues_project_id',
using: nil,
where: nil,
@@ -1820,7 +1820,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id),
+ %w[gl_project_id],
{
unique: false,
name: 'index_on_issues_gl_project_id',
@@ -1835,7 +1835,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'using a regular index with multiple columns' do
it 'copies the index' do
index = double(:index,
- columns: %w(project_id foobar),
+ columns: %w[project_id foobar],
name: 'index_on_issues_project_id_foobar',
using: nil,
where: nil,
@@ -1849,7 +1849,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id foobar),
+ %w[gl_project_id foobar],
{
unique: false,
name: 'index_on_issues_gl_project_id_foobar',
@@ -1864,7 +1864,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'using an index with a WHERE clause' do
it 'copies the index' do
index = double(:index,
- columns: %w(project_id),
+ columns: %w[project_id],
name: 'index_on_issues_project_id',
using: nil,
where: 'foo',
@@ -1878,7 +1878,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id),
+ %w[gl_project_id],
{
unique: false,
name: 'index_on_issues_gl_project_id',
@@ -1894,7 +1894,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'using an index with a USING clause' do
it 'copies the index' do
index = double(:index,
- columns: %w(project_id),
+ columns: %w[project_id],
name: 'index_on_issues_project_id',
where: nil,
using: 'foo',
@@ -1908,7 +1908,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id),
+ %w[gl_project_id],
{
unique: false,
name: 'index_on_issues_gl_project_id',
@@ -1924,7 +1924,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
context 'using an index with custom operator classes' do
it 'copies the index' do
index = double(:index,
- columns: %w(project_id),
+ columns: %w[project_id],
name: 'index_on_issues_project_id',
using: nil,
where: nil,
@@ -1938,7 +1938,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id),
+ %w[gl_project_id],
{
unique: false,
name: 'index_on_issues_gl_project_id',
@@ -1955,7 +1955,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
it 'copies the index' do
index = double(:index,
{
- columns: %w(project_id foobar),
+ columns: %w[project_id foobar],
name: 'index_on_issues_project_id_foobar',
using: :gin,
where: nil,
@@ -1970,7 +1970,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id foobar),
+ %w[gl_project_id foobar],
{
unique: false,
name: 'index_on_issues_gl_project_id_foobar',
@@ -1988,7 +1988,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
it 'copies the index' do
index = double(:index,
{
- columns: %w(project_id foobar),
+ columns: %w[project_id foobar],
name: 'index_on_issues_project_id_foobar',
using: :gin,
where: nil,
@@ -2003,7 +2003,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
expect(model).to receive(:add_concurrent_index)
.with(:issues,
- %w(gl_project_id foobar),
+ %w[gl_project_id foobar],
{
unique: false,
name: 'index_on_issues_gl_project_id_foobar',
@@ -2020,7 +2020,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
describe 'using an index of which the name does not contain the source column' do
it 'raises RuntimeError' do
index = double(:index,
- columns: %w(project_id),
+ columns: %w[project_id],
name: 'index_foobar_index',
using: nil,
where: nil,
diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb
index d8a2612caf3..2e654a33a58 100644
--- a/spec/lib/gitlab/database/postgres_index_spec.rb
+++ b/spec/lib/gitlab/database/postgres_index_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do
it 'only btree and gist indexes' do
types = described_class.reindexing_support.map(&:type).uniq
- expect(types & %w(btree gist)).to eq(types)
+ expect(types & %w[btree gist]).to eq(types)
end
context 'with leftover indexes' do
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do
end
it 'retrieves leftover indexes matching the /_ccnew[0-9]*$/ pattern' do
- expect(subject.map(&:name)).to eq(%w(foobar_ccnew foobar_ccnew1))
+ expect(subject.map(&:name)).to eq(%w[foobar_ccnew foobar_ccnew1])
end
end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb
new file mode 100644
index 00000000000..042bc297520
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Columns,
+ feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ let_it_be(:namespace_columns) { Namespace.column_names.join(',') }
+
+ describe '.types' do
+ let(:node) { sql_select_node(sql) }
+ let(:cte_refs) { {} }
+ let(:select_stmt) do
+ Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt.new(node, cte_refs)
+ end
+
+ subject { described_class.types(select_stmt) }
+
+ context 'when static column' do
+ let(:sql) { 'SELECT id FROM namespaces' }
+
+ it do
+ expect(subject).to contain_exactly(Type::STATIC)
+ end
+
+ context 'with dynamic reference' do
+ let(:sql) { 'SELECT id FROM (SELECT * FROM namespaces) AS xyz' }
+
+ it do
+ expect(subject).to contain_exactly(Type::STATIC)
+ end
+ end
+ end
+
+ context 'when dynamic column' do
+ let(:sql) { 'SELECT * FROM namespaces' }
+
+ it do
+ expect(subject).to contain_exactly(Type::DYNAMIC)
+ end
+
+ context 'with static reference' do
+ let(:sql) { 'SELECT * FROM (SELECT 1) AS xyz' }
+
+ it do
+ expect(subject).to contain_exactly(Type::STATIC)
+ end
+ end
+ end
+
+ context 'when reference has errors' do
+ let(:cte_refs) { { 'namespaces' => [Type::INVALID].to_set } }
+ let(:sql) { 'SELECT * FROM namespaces' }
+
+ it 'forward through error state' do
+ expect(subject).to include(Type::INVALID)
+ end
+ end
+
+ context 'when static and dynamic columns' do
+ let(:sql) { 'SELECT *, users.id FROM namespaces, users' }
+
+ it do
+ expect(subject).to contain_exactly(Type::DYNAMIC, Type::STATIC)
+ end
+ end
+
+ context 'when static column and error' do
+ let(:error_column) { "SELECT #{namespace_columns} FROM namespaces UNION SELECT * FROM namespaces" }
+ let(:sql) { "SELECT id, (#{error_column}) FROM namespaces" }
+
+ it do
+ expect(subject).to contain_exactly(Type::STATIC, Type::INVALID)
+ end
+ end
+
+ context 'when dynamic column and error' do
+ let(:error_column) { "SELECT #{namespace_columns} FROM namespaces UNION SELECT * FROM namespaces" }
+ let(:sql) { "SELECT *, (#{error_column}) FROM namespaces" }
+
+ it do
+ # The sub-select is treated as a Type::STATIC column for now. This could do with some refinement.
+ expect(subject).to contain_exactly(Type::DYNAMIC, Type::STATIC, Type::INVALID)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb
new file mode 100644
index 00000000000..eacaa643ba5
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::CommonTableExpressions,
+ feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ describe '.references' do
+ let(:node) { sql_select_node(sql) }
+ let(:cte_refs) { {} }
+
+ subject { described_class.references(node, cte_refs) }
+
+ context 'when standard CTE' do
+ let(:sql) do
+ <<-SQL
+ WITH some_cte AS (#{cte})
+ SELECT 1
+ FROM some_cte
+ SQL
+ end
+
+ context 'with static SELECT' do
+ let(:cte) { 'SELECT 1' }
+
+ it do
+ exp = { "some_cte" => Set.new([Type::STATIC]) }
+ expect(subject).to eq(exp)
+ end
+ end
+
+ context 'with dynamic SELECT' do
+ let(:cte) { 'SELECT * FROM namespaces' }
+
+ it do
+ exp = { "some_cte" => Set.new([Type::DYNAMIC]) }
+ expect(subject).to eq(exp)
+ end
+ end
+ end
+
+ context 'when recursive CTE' do
+ let(:sql) do
+ <<-SQL
+ WITH RECURSIVE some_cte AS (#{cte})
+ SELECT 1
+ FROM some_cte
+ SQL
+ end
+
+ context 'with static SELECT' do
+ let(:cte) { 'SELECT 1 UNION SELECT 2' }
+
+ it do
+ exp = { "some_cte" => Set.new([Type::STATIC]) }
+ expect(subject).to eq(exp)
+ end
+ end
+
+ context 'with dynamic SELECT' do
+ let(:cte) { 'SELECT * FROM namespaces UNION SELECT * FROM namespaces' }
+
+ it do
+ exp = { "some_cte" => Set.new([Type::DYNAMIC]) }
+ expect(subject).to eq(exp)
+ end
+ end
+
+ context 'with error SELECT' do
+ let(:cte) { 'SELECT * FROM namespaces UNION SELECT id FROM namespaces' }
+
+ it do
+ exp = { "some_cte" => Set.new([Type::DYNAMIC, Type::STATIC, Type::INVALID]) }
+ expect(subject).to eq(exp)
+ end
+ end
+ end
+
+ context 'with inherited CTE references' do
+ let(:sql) do
+ <<-SQL
+ WITH some_cte AS (SELECT 1)
+ SELECT 1
+ FROM some_cte
+ SQL
+ end
+
+ let(:cte_refs) { { 'some_reference' => 123 } }
+
+ it 'maintains inherited CTE references' do
+ subject_ref_names = subject.keys
+ expect(subject_ref_names).to eq(cte_refs.keys + ['some_cte'])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb
new file mode 100644
index 00000000000..03c0a845e60
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Froms,
+ feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ describe '.references' do
+ let(:node) { sql_select_node(sql) }
+ let(:cte_refs) { {} }
+
+ subject { described_class.references(node, cte_refs) }
+
+ context 'when node is nil' do
+ let(:node) { nil }
+
+ it { is_expected.to eq({}) }
+ end
+
+ context 'when range_var' do
+ let(:sql) { 'SELECT 1 FROM namespaces' }
+
+ it { is_expected.to match({ 'namespaces' => an_instance_of(PgQuery::RangeVar) }) }
+ end
+
+ context 'when range_var with alias' do
+ let(:sql) { 'SELECT 1 FROM namespaces ns' }
+
+ it { is_expected.to match({ 'ns' => an_instance_of(PgQuery::RangeVar) }) }
+ end
+
+ context 'when join expression' do
+ let(:sql) do
+ <<-SQL
+ SELECT 1 FROM namespaces
+ INNER JOIN organizations ON namespaces.organization_id = organization.id
+ SQL
+ end
+
+ it do
+ is_expected.to match({
+ 'namespaces' => an_instance_of(PgQuery::RangeVar),
+ 'organizations' => an_instance_of(PgQuery::RangeVar)
+ })
+ end
+ end
+
+ context 'when join expression with alias' do
+ let(:sql) do
+ <<-SQL
+ SELECT 1 FROM namespaces ns
+ INNER JOIN organizations o ON ns.organization_id = o.id
+ SQL
+ end
+
+ it do
+ is_expected.to match({
+ 'ns' => an_instance_of(PgQuery::RangeVar),
+ 'o' => an_instance_of(PgQuery::RangeVar)
+ })
+ end
+ end
+
+ context 'when sub-query' do
+ let(:sql) do
+ <<-SQL
+ SELECT 1
+ FROM (SELECT 1) some_subquery
+ SQL
+ end
+
+ it { is_expected.to match({ 'some_subquery' => [Type::STATIC].to_set }) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb
new file mode 100644
index 00000000000..a8294376107
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Node, feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ let(:sql) { 'SELECT id FROM namespaces' }
+ let(:node) { sql_select_node(sql) }
+
+ describe '.descendants' do
+ context 'with a block' do
+ it do
+ nodes = []
+ described_class.descendants(node.from_clause) do |node|
+ nodes << node.class
+ end
+ expect(nodes).to match_array [PgQuery::Node, PgQuery::RangeVar]
+ end
+ end
+
+ context 'without a block' do
+ subject { described_class.descendants(node) }
+
+ it { is_expected.to be_instance_of Enumerator }
+ end
+
+ context 'with a filter' do
+ let(:filter) { ->(field) { %i[from_clause target_list].include?(field) } }
+
+ subject { described_class.descendants(node, filter: filter).count }
+
+ it 'only traverse nodes that match the filter' do
+ is_expected.to eq 2
+ end
+ end
+ end
+
+ describe '.locate_descendant' do
+ subject { described_class.locate_descendant(node.target_list, :res_target) }
+
+ it { is_expected.to be_instance_of PgQuery::ResTarget }
+
+ context 'with a filter' do
+ subject { described_class.locate_descendant(node.target_list, :res_target, filter: ->(_) { false }) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '.locate_descendants' do
+ subject { described_class.locate_descendants(node.target_list, :res_target) }
+
+ it { is_expected.to be_instance_of Array }
+
+ context 'with a filter' do
+ subject { described_class.locate_descendant(node.target_list, :res_target, filter: ->(_) { false }) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '.dig' do
+ subject { described_class.dig(node.target_list[0], :res_target, :val, :column_ref) }
+
+ it { is_expected.to be_instance_of PgQuery::ColumnRef }
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb
new file mode 100644
index 00000000000..0f0f92aa1f2
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::References,
+ feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ let(:refs) do
+ {
+ 'resolved_reference' => Set.new,
+ 'unresolved_reference' => double,
+ 'table_reference' => PgQuery::RangeVar.new,
+ 'error_reference' => [Type::INVALID].to_set
+ }
+ end
+
+ describe '.resolved' do
+ subject { described_class.resolved(refs) }
+
+ it { is_expected.to eq refs.slice('resolved_reference', 'error_reference') }
+ end
+
+ describe '.unresolved' do
+ subject { described_class.unresolved(refs) }
+
+ it { is_expected.to eq refs.slice('unresolved_reference') }
+ end
+
+ describe '.errors?' do
+ subject { described_class.errors?(refs) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when no errors exist' do
+ subject { described_class.errors?(refs.except('error_reference')) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb
new file mode 100644
index 00000000000..52d6c9f1032
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb
@@ -0,0 +1,361 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt, feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ let_it_be(:static_namespace_columns) { Namespace.column_names.join(', ') }
+
+ let(:node) { sql_select_node(sql) }
+
+ subject { described_class.new(node).types }
+
+ shared_examples 'valid SQL' do
+ it { is_expected.not_to include(Type::INVALID) }
+ end
+
+ shared_examples 'invalid SQL' do
+ it { is_expected.to include(Type::INVALID) }
+ end
+
+ shared_context 'with basic set operator queries' do
+ let(:set_operator_queries) do
+ {
+ 'set operator with static columns' => <<-SQL,
+ SELECT id, name FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT id, name FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with static referenced columns' => <<-SQL,
+ SELECT namespaces.id, name FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT id, namespaces.name FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with static alias referenced columns' => <<-SQL,
+ SELECT namespaces.id, name FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT id, namespaces2.name FROM namespaces2 WHERE name = 'test2'
+ SQL
+ 'set operator with dynamic columns' => <<-SQL,
+ SELECT * FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT * FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with dynamic referenced columns' => <<-SQL,
+ SELECT namespaces.* FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT namespaces.* FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with dynamic referenced aliased columns' => <<-SQL,
+ SELECT namespaces.* FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT namespaces2.* FROM namespaces namespaces2 WHERE name = 'test2'
+ SQL
+ 'set operator with dynamic columns without using star' => <<-SQL,
+ SELECT namespaces FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT * FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with single dynamic referenced columns' => <<-SQL,
+ SELECT namespaces.* FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT * FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with static and dynamic columns' => <<-SQL,
+ SELECT #{static_namespace_columns} FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT * FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with static aliased columns and dynamic columns' => <<-SQL,
+ SELECT #{Namespace.column_names.map { |c| "namespaces2.#{c}" }.join(', ')}
+ FROM namespaces namespaces2
+ WHERE name = 'test1'
+ #{set_operator}
+ SELECT * FROM namespaces WHERE name = 'test2'
+ SQL
+ 'set operator with static columns and dynamic aliased columns' => <<-SQL,
+ SELECT #{static_namespace_columns} FROM namespaces WHERE name = 'test1'
+ #{set_operator}
+ SELECT namespaces2.* FROM namespaces namespaces2 WHERE name = 'test2'
+ SQL
+ 'set operator with static and dynamic aliased columns' => <<-SQL,
+ SELECT #{Namespace.column_names.map { |c| "namespaces2.#{c}" }.join(', ')}
+ FROM namespaces namespaces2
+ WHERE name = 'test1'
+ #{set_operator}
+ SELECT namespaces3.* FROM namespaces namespaces3 WHERE name = 'test2'
+ SQL
+ 'set operator with mixed dynamic and static columns' => <<-SQL,
+ SELECT namespaces.*, projects.id FROM namespaces, projects WHERE name = 'test1'
+ #{set_operator}
+ SELECT namespaces.*, projects.id FROM namespaces, projects WHERE name = 'test2'
+ SQL
+ 'set operator without references' => <<-SQL
+ SELECT 1
+ #{set_operator}
+ SELECT 2
+ SQL
+ }
+ end
+
+ where(:query_name, :behavior) do
+ [
+ ['set operator with static columns', 'valid SQL'],
+ ['set operator with static referenced columns', 'valid SQL'],
+ ['set operator with static alias referenced columns', 'valid SQL'],
+ ['set operator with dynamic columns', 'valid SQL'],
+ ['set operator with dynamic referenced columns', 'valid SQL'],
+ ['set operator with dynamic referenced aliased columns', 'valid SQL'],
+ ['set operator with dynamic columns without using star', 'invalid SQL'],
+ ['set operator with single dynamic referenced columns', 'valid SQL'],
+ ['set operator with static and dynamic columns', 'invalid SQL'],
+ ['set operator with static and dynamic aliased columns', 'invalid SQL'],
+ ['set operator with static aliased columns and dynamic columns', 'invalid SQL'],
+ ['set operator with static columns and dynamic aliased columns', 'invalid SQL'],
+ ['set operator with static and dynamic aliased columns', 'invalid SQL'],
+ ['set operator with mixed dynamic and static columns', 'valid SQL'],
+ ['set operator without references', 'valid SQL']
+ ]
+ end
+ end
+
+ %w[UNION INTERSECT EXCEPT].each do |set_operator|
+ context "with #{set_operator}" do
+ let(:set_operator) { set_operator }
+
+ context "for basic #{set_operator} queries" do
+ include_context 'with basic set operator queries'
+
+ with_them do
+ let(:sql) { set_operator_queries[query_name] }
+
+ it_behaves_like params[:behavior]
+ end
+ end
+
+ context 'for subquery' do
+ context "with #{set_operator}" do
+ where(:select_columns) do
+ [
+ ['*'],
+ ['sub.*'],
+ ['sub'],
+ ['sub.id']
+ ]
+ end
+
+ with_them do
+ include_context 'with basic set operator queries'
+
+ with_them do
+ let(:sql) do
+ <<-SQL
+ SELECT #{select_columns}
+ FROM (
+ #{set_operator_queries[query_name]}
+ ) sub
+ SQL
+ end
+
+ it_behaves_like params[:behavior]
+ end
+ end
+ end
+
+ context "when used by one side of #{set_operator}" do
+ let(:sql) do
+ <<-SQL
+ SELECT #{union1}
+ FROM (
+ SELECT #{subquery}
+ FROM namespaces
+ ) namespaces
+
+ #{set_operator}
+
+ SELECT #{union2}
+ FROM namespaces
+ SQL
+ end
+
+ where(:union1, :union2, :subquery, :expected) do
+ [
+ ['*', '*', '*', 'valid SQL'],
+ [ref(:static_namespace_columns), '*', '*', 'invalid SQL'],
+ ['*', ref(:static_namespace_columns), '*', 'invalid SQL'],
+ ['*', '*', ref(:static_namespace_columns), 'invalid SQL'],
+ [ref(:static_namespace_columns), ref(:static_namespace_columns), '*', 'valid SQL'],
+ [ref(:static_namespace_columns), '*', ref(:static_namespace_columns), 'invalid SQL'],
+ ['*', ref(:static_namespace_columns), ref(:static_namespace_columns), 'valid SQL'],
+ ['namespaces', 'namespaces', 'namespaces', 'valid SQL'],
+ # Used by our keyset pagination queries.
+ ['NULL :: namespaces', 'namespaces', 'id, name', 'valid SQL'],
+ ['NULL :: namespaces, id, name', 'namespaces, id, name', 'namespaces', 'valid SQL']
+ ]
+ end
+
+ with_them do
+ it_behaves_like params[:expected]
+ end
+ end
+ end
+
+ context 'for CTE' do
+ context "when #{set_operator}" do
+ where(:select_columns) do
+ [
+ ['*'],
+ ['namespaces_cte.*'],
+ ['namespaces_cte.id']
+ ]
+ end
+
+ with_them do
+ include_context 'with basic set operator queries'
+
+ with_them do
+ let(:sql) do
+ <<-SQL
+ WITH namespaces_cte AS (
+ #{set_operator_queries[query_name]}
+ )
+ SELECT *
+ FROM namespaces_cte
+ SQL
+ end
+
+ it_behaves_like params[:behavior]
+ end
+ end
+ end
+
+ context "when used by one side of #{set_operator}" do
+ let(:sql) do
+ <<-SQL
+ WITH #{cte_name} AS (
+ SELECT #{cte_select_columns}
+ FROM namespaces
+ )
+ SELECT #{select_columns}
+ FROM #{cte_name}
+
+ #{set_operator}
+
+ SELECT *
+ FROM namespaces
+ SQL
+ end
+
+ where(:cte_select_columns, :select_columns, :cte_name, :expected) do
+ [
+ ['*', '*', 'some_cte', 'valid SQL'],
+ [ref(:static_namespace_columns), '*', 'some_cte', 'invalid SQL'],
+ ['*', ref(:static_namespace_columns), 'some_cte', 'invalid SQL'],
+ [ref(:static_namespace_columns), ref(:static_namespace_columns), 'some_cte', 'invalid SQL'],
+ ['*', '*', 'some_cte', 'valid SQL'],
+ # Same scenarios as above, but the CTE name matches the table name in the CTE.
+ ['*', '*', 'namespaces', 'valid SQL'],
+ [ref(:static_namespace_columns), '*', 'namespaces', 'valid SQL'],
+ ['*', ref(:static_namespace_columns), 'namespaces', 'invalid SQL'],
+ [ref(:static_namespace_columns), ref(:static_namespace_columns), 'namespaces', 'valid SQL'],
+ ['*', '*', 'namespaces', 'valid SQL']
+ ]
+ end
+
+ with_them do
+ it_behaves_like params[:expected]
+ end
+ end
+
+ context 'when recursive' do
+ let(:sql) do
+ <<-SQL
+ WITH RECURSIVE namespaces_cte AS (
+ (
+ SELECT #{select1}
+ FROM namespaces
+ )
+ UNION
+ (
+ SELECT #{select2}
+ FROM namespaces_cte
+ )
+ )
+ SELECT *
+ FROM namespaces_cte
+ SQL
+ end
+
+ where(:select1, :select2, :expected) do
+ [
+ ['id', 'id', 'valid SQL'],
+ [ref(:static_namespace_columns), '*', 'valid SQL'],
+ ['*', ref(:static_namespace_columns), 'invalid SQL']
+ ]
+ end
+
+ with_them do
+ it_behaves_like params[:expected]
+ end
+ end
+ end
+
+ context 'for subselect' do
+ context 'with set operator' do
+ let(:sql) do
+ <<-SQL
+ SELECT (
+ SELECT id FROM namespaces
+ #{set_operator}
+ SELECT id FROM namespaces
+ ) AS namespace_id
+ SQL
+ end
+
+ it_behaves_like 'valid SQL'
+ end
+ end
+ end
+ end
+
+ context 'with lateral join' do
+ let(:sql) do
+ <<-SQL
+ SELECT namespaces.id
+ FROM
+ namespaces CROSS
+ JOIN LATERAL (
+ SELECT
+ namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1) ] AS id
+ FROM
+ namespaces
+ WHERE
+ namespaces.type = 'Group'
+ AND namespaces.traversal_ids @ > ARRAY[members.source_id]
+ ) namespaces
+ SQL
+ end
+
+ pending
+ end
+
+ context 'when columns are not referenced' do
+ let(:sql) do
+ <<-SQL
+ SELECT
+ COUNT(1)
+ FROM (
+ SELECT #{static_namespace_columns}
+ FROM namespaces
+ UNION
+ SELECT *
+ FROM namespaces
+ ) invalid_union
+ SQL
+ end
+
+ # Error will bubble up even though the parent query does not reference any of the sub-query columns.
+ it_behaves_like 'invalid SQL'
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb
new file mode 100644
index 00000000000..2ea69f3726e
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Targets, feature_category: :cell do
+ include PreventSetOperatorMismatchHelper
+
+ let(:node) { sql_select_node(sql) }
+ let(:select_stmt) { Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt.new(node) }
+ let(:target) { node.target_list[0].res_target }
+
+ describe '.reference_names' do
+ subject { described_class.reference_names(target, select_stmt) }
+
+ context 'with a literal target' do
+ let(:sql) { 'SELECT 1' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with a function target' do
+ let(:sql) { 'SELECT unnest(ARRAY[1,2]) FROM namespaces, users' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with a subselect target' do
+ let(:sql) { 'SELECT (SELECT 1) xyz FROM namespaces' }
+
+ it { is_expected.to eq(%w[xyz_subselect]) }
+
+ it 'updates all_references in the select statement' do
+ expect { subject }.to change { select_stmt.all_references }
+ .to include('xyz_subselect')
+ end
+ end
+
+ context 'with an unqualified column name' do
+ let(:sql) { 'SELECT id FROM namespaces, users' }
+
+ it { is_expected.to eq(%w[namespaces users]) }
+ end
+
+ context 'with a qualified column name' do
+ let(:sql) { 'SELECT namespaces.id FROM namespaces, users' }
+
+ it { is_expected.to eq(%w[namespaces]) }
+ end
+
+ context 'with a table name' do
+ let(:sql) { 'SELECT namespaces FROM namespaces, users' }
+
+ it { is_expected.to eq(%w[namespaces]) }
+ end
+
+ context 'with a *' do
+ let(:sql) { 'SELECT * FROM namespaces, users' }
+
+ it { is_expected.to eq(%w[namespaces users]) }
+ end
+ end
+
+ describe '.a_star?' do
+ subject { described_class.a_star?(target) }
+
+ context 'when * is used' do
+ let(:sql) { 'SELECT * FROM namespaces' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when no * is used' do
+ let(:sql) { 'SELECT 1' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '.null?' do
+ subject { described_class.null?(target) }
+
+ context 'when target is null' do
+ let(:sql) { 'SELECT NULL::namespaces FROM namespaces' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when target is not null' do
+ let(:sql) { 'SELECT 1' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb
new file mode 100644
index 00000000000..28c155c1eb1
--- /dev/null
+++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch, query_analyzers: false, feature_category: :cell do
+ let(:analyzer) { described_class }
+ let_it_be(:static_namespace_columns) { Namespace.column_names.join(', ') }
+
+ def process_sql(sql, model = ApplicationRecord)
+ Gitlab::Database::QueryAnalyzer.instance.within([analyzer]) do
+ # Skip load balancer and retrieve connection assigned to model
+ Gitlab::Database::QueryAnalyzer.instance.send(:process_sql, sql, model.retrieve_connection)
+ end
+ end
+
+ shared_examples 'parses SQL' do
+ it do
+ expect_next_instance_of(described_class::SelectStmt) do |select_stmt|
+ expect(select_stmt).to receive(:types).and_return(Set.new)
+ end
+
+ process_sql sql
+ end
+ end
+
+ context 'when SQL includes a UNION' do
+ let(:sql) { 'SELECT 1 UNION SELECT 2' }
+
+ include_examples 'parses SQL'
+ end
+
+ context 'when SQL includes a INTERSECT' do
+ let(:sql) { 'SELECT 1 INTERSECT SELECT 2' }
+
+ include_examples 'parses SQL'
+ end
+
+ context 'when SQL includes a EXCEPT' do
+ let(:sql) { 'SELECT 1 EXCEPT SELECT 2' }
+
+ include_examples 'parses SQL'
+ end
+
+ context 'when SQL does not include a set operator' do
+ let(:sql) { 'SELECT 1' }
+
+ it 'does not parse SQL' do
+ expect(described_class::SelectStmt).not_to receive(:new)
+
+ process_sql sql
+ end
+ end
+
+ context 'when SQL is invalid' do
+ it 'raises error' do
+ expect do
+ process_sql "SELECT #{static_namespace_columns} FROM namespaces UNION SELECT * FROM namespaces"
+ end.to raise_error(described_class::SetOperatorStarError)
+ end
+ end
+
+ context 'when SQL is valid' do
+ it 'does not raise error' do
+ expect do
+ process_sql 'SELECT 1'
+ end.not_to raise_error
+ end
+ end
+
+ context 'when SQL has many select statements' do
+ let(:sql) do
+ <<-SQL
+ SELECT 1 UNION SELECT 1;
+ SELECT #{static_namespace_columns} FROM namespaces UNION SELECT * FROM namespaces
+ SQL
+ end
+
+ it 'raises error' do
+ expect do
+ process_sql sql
+ end.to raise_error(described_class::SetOperatorStarError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index 441f6476abe..2321f5d933d 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -231,7 +231,7 @@ RSpec.describe Gitlab::Database::Reindexing, feature_category: :database, time_t
states = queued_actions.map(&:reload).map(&:state)
- expect(states).to eq(%w(failed done queued))
+ expect(states).to eq(%w[failed done queued])
end
end
diff --git a/spec/lib/gitlab/database/transaction/observer_spec.rb b/spec/lib/gitlab/database/transaction/observer_spec.rb
index d1cb014a594..778212add66 100644
--- a/spec/lib/gitlab/database/transaction/observer_spec.rb
+++ b/spec/lib/gitlab/database/transaction/observer_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Database::Transaction::Observer, feature_category: :datab
User.first
expect(transaction_context).to be_a(::Gitlab::Database::Transaction::Context)
- expect(context.keys).to match_array(%i(start_time depth savepoints queries backtraces external_http_count_start external_http_duration_start))
+ expect(context.keys).to match_array(%i[start_time depth savepoints queries backtraces external_http_count_start external_http_duration_start])
expect(context[:depth]).to eq(2)
expect(context[:savepoints]).to eq(1)
expect(context[:queries].length).to eq(1)
diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
index 2811bc859da..ef61a962279 100644
--- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
@@ -46,8 +46,8 @@ RSpec.describe Gitlab::DependencyLinker::BaseLinker do
'target="_blank"'
]
- attrs.unshift(%{href="#{url}"}) if url
+ attrs.unshift(%(href="#{url}")) if url
- %{<a #{attrs.join(' ')}>#{text}</a>}
+ %(<a #{attrs.join(' ')}>#{text}</a>)
end
end
diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
index 7f6b3b86799..d147afbf3bd 100644
--- a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
@@ -70,7 +70,7 @@ RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links dependencies' do
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 52ddba24458..eeb7471cd18 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe Gitlab::DependencyLinker::CartfileLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links dependencies' do
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index 02fac96a02f..cf636a7f201 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe Gitlab::DependencyLinker::ComposerJsonLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'does not link the module name' do
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index 00e95dea224..a5507fab3fe 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::GemfileLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links sources' do
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index ae82dd51c95..9f207459113 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Gitlab::DependencyLinker::GemspecLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'does not link the gem name' do
diff --git a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
index 605b14bc923..fbd8b6477a2 100644
--- a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::DependencyLinker::GoModLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links the module name' do
diff --git a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
index 2836c0e9f29..559c27e91ba 100644
--- a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::GoSumLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links modules' do
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index c1ed030c548..3ac234f47d9 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe Gitlab::DependencyLinker::GodepsJsonLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links the package name' do
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index cdfc0e89bc7..127f437dd54 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'does not link the module name' do
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 8e536c00ea6..41c29278bda 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::PodfileLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links sources' do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index 1f81049a41e..f8b782a7cda 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::DependencyLinker::PodspecJsonLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links the gem name' do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index 132b5b21d85..6f2653829e2 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::DependencyLinker::PodspecLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'does not link the pod name' do
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 86ebddc9681..fc3c57b7cff 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Gitlab::DependencyLinker::RequirementsTxtLinker do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>)
end
it 'links dependencies' do
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index e39c15c8fd7..e65f5a618a5 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -44,13 +44,13 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen
end
it 'highlights and marks removed lines' do
- code = %{-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %(-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n)
expect(subject[4].rich_text).to eq(code)
end
it 'highlights and marks added lines' do
- code = %{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %(+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n)
expect(subject[5].rich_text).to eq(code)
end
@@ -86,14 +86,14 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen
end
it 'marks removed lines' do
- code = %q{- raise "System commands must be given as an array of strings"}
+ code = %q(- raise "System commands must be given as an array of strings")
expect(subject[4].text).to eq(code)
expect(subject[4].text).not_to be_html_safe
end
it 'marks added lines' do
- code = %q{+ raise <span class="idiff left right addition">RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+ code = %q(+ raise <span class="idiff left right addition">RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;)
expect(subject[5].rich_text).to eq(code)
expect(subject[5].rich_text).to be_html_safe
@@ -107,7 +107,7 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen
it 'keeps the original rich line' do
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
- code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"}
+ code = %q(+ raise RuntimeError, "System commands must be given as an array of strings")
expect(subject[5].text).to eq(code)
expect(subject[5].text).not_to be_html_safe
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 8ab2a7b64dd..602f1b1c2b2 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -10,10 +10,10 @@ RSpec.describe Gitlab::Diff::InlineDiffMarker do
subject { described_class.new(raw, rich).mark(inline_diffs) }
context "when the rich text is html safe" do
- let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
+ let(:rich) { %(<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>).html_safe }
it 'marks the range' do
- expect(subject).to eq(%{<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">&#39;d</span>ef&#39;</span>})
+ expect(subject).to eq(%(<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">&#39;d</span>ef&#39;</span>))
expect(subject).to be_html_safe
end
end
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Diff::InlineDiffMarker do
let(:rich) { "abc 'def' differs" }
it 'marks the range' do
- expect(subject).to eq(%{ab<span class="idiff left right">c &#39;d</span>ef&#39; differs})
+ expect(subject).to eq(%(ab<span class="idiff left right">c &#39;d</span>ef&#39; differs))
expect(subject).to be_html_safe
end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 00e68f73d2d..e8604256f71 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -244,7 +244,7 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
- include_examples 'search results filtered by archived', 'search_issues_hide_archived_projects'
+ include_examples 'search results filtered by archived'
end
context 'ordering' do
diff --git a/spec/models/activity_pub/releases_subscription_spec.rb b/spec/models/activity_pub/releases_subscription_spec.rb
new file mode 100644
index 00000000000..0c873a5c18a
--- /dev/null
+++ b/spec/models/activity_pub/releases_subscription_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ActivityPub::ReleasesSubscription, type: :model, feature_category: :release_orchestration do
+ describe 'factory' do
+ subject { build(:activity_pub_releases_subscription) }
+
+ it { is_expected.to be_valid }
+ end
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project).optional(false) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:subscriber_url) }
+
+ describe 'subscriber_url' do
+ subject { build(:activity_pub_releases_subscription) }
+
+ it { is_expected.to validate_uniqueness_of(:subscriber_url).case_insensitive.scoped_to([:project_id]) }
+ it { is_expected.to allow_value("http://example.com/actor").for(:subscriber_url) }
+ it { is_expected.not_to allow_values("I'm definitely not a URL").for(:subscriber_url) }
+ end
+
+ describe 'subscriber_inbox_url' do
+ subject { build(:activity_pub_releases_subscription) }
+
+ it { is_expected.to validate_uniqueness_of(:subscriber_inbox_url).case_insensitive.scoped_to([:project_id]) }
+ it { is_expected.to allow_value("http://example.com/actor").for(:subscriber_inbox_url) }
+ it { is_expected.not_to allow_values("I'm definitely not a URL").for(:subscriber_inbox_url) }
+ end
+
+ describe 'shared_inbox_url' do
+ subject { build(:activity_pub_releases_subscription) }
+
+ it { is_expected.to allow_value("http://example.com/actor").for(:shared_inbox_url) }
+ it { is_expected.not_to allow_values("I'm definitely not a URL").for(:shared_inbox_url) }
+ end
+
+ describe 'payload' do
+ it { is_expected.not_to allow_value("string").for(:payload) }
+ it { is_expected.not_to allow_value(1.0).for(:payload) }
+
+ it do
+ is_expected.to allow_value({
+ '@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'
+ }).for(:payload)
+ end
+ end
+ end
+
+ describe '.find_by_subscriber_url' do
+ let_it_be(:subscription) { create(:activity_pub_releases_subscription) }
+
+ it 'returns a record if arguments match' do
+ result = described_class.find_by_subscriber_url(subscription.subscriber_url)
+
+ expect(result).to eq(subscription)
+ end
+
+ it 'returns a record if arguments match case insensitively' do
+ result = described_class.find_by_subscriber_url(subscription.subscriber_url.upcase)
+
+ expect(result).to eq(subscription)
+ end
+
+ it 'returns nil if project does not match' do
+ result = described_class.find_by_subscriber_url('I really should not exist')
+
+ expect(result).to be(nil)
+ end
+ end
+end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 3a3ef072b28..6d480badaa2 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -524,6 +524,19 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
+ describe '.with_creator_id' do
+ subject { described_class.with_creator_id('1') }
+
+ let_it_be(:runner1) { create(:ci_runner, creator_id: 2) }
+ let_it_be(:runner2) { create(:ci_runner, creator_id: 1) }
+ let_it_be(:runner3) { create(:ci_runner, creator_id: 1) }
+ let_it_be(:runner4) { create(:ci_runner, creator_id: nil) }
+
+ it 'returns runners with creator_id \'1\'' do
+ is_expected.to contain_exactly(runner2, runner3)
+ end
+ end
+
describe '.stale', :freeze_time do
subject { described_class.stale }
diff --git a/spec/models/concerns/reset_on_column_errors_spec.rb b/spec/models/concerns/reset_on_column_errors_spec.rb
index 38ba0f447f5..96bee128f7e 100644
--- a/spec/models/concerns/reset_on_column_errors_spec.rb
+++ b/spec/models/concerns/reset_on_column_errors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ResetOnColumnErrors, :delete, feature_category: :shared do
+RSpec.describe ResetOnColumnErrors, :delete, feature_category: :shared, query_analyzers: false do
let(:test_reviewer_model) do
Class.new(ApplicationRecord) do
self.table_name = '_test_reviewers_table'
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 37bcdf61d23..33d840cafd7 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -5,13 +5,15 @@ require 'spec_helper'
RSpec.describe 'Adding a Note', feature_category: :team_planning do
include GraphqlHelpers
- let_it_be(:current_user) { create(:user) }
-
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group).tap { |g| g.add_developer(developer) } }
+ let_it_be_with_reload(:project) { create(:project, :repository, group: group) }
+ let_it_be(:developer) { create(:user).tap { |u| group.add_developer(u) } }
let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
- let(:project) { create(:project, :repository) }
let(:discussion) { nil }
let(:head_sha) { nil }
let(:body) { 'Body text' }
+ let(:current_user) { user }
let(:mutation) do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
@@ -30,9 +32,7 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
it_behaves_like 'a Note mutation when the user does not have permission'
context 'when the user has permission' do
- before do
- project.add_developer(current_user)
- end
+ let(:current_user) { developer }
it_behaves_like 'a working GraphQL mutation'
@@ -78,8 +78,10 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'for an issue' do
- let(:noteable) { create(:issue, project: project) }
+ let_it_be_with_reload(:issue) { create(:issue, project: project) }
+ let(:noteable) { issue }
let(:mutation) { graphql_mutation(:create_note, variables) }
+ let(:variables_extra) { {} }
let(:variables) do
{
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
@@ -87,10 +89,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
}.merge(variables_extra)
end
- before do
- project.add_developer(current_user)
- end
-
context 'when using internal param' do
let(:variables_extra) { { internal: true } }
@@ -104,8 +102,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'as work item' do
- let_it_be(:project) { create(:project) }
- let_it_be(:noteable) { create(:work_item, project: project) }
+ let_it_be_with_reload(:work_item) { create(:work_item, :task, project: project) }
+ let(:noteable) { work_item }
context 'when using internal param' do
let(:variables_extra) { { internal: true } }
@@ -120,10 +118,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'without notes widget' do
- let(:variables_extra) { {} }
-
before do
- WorkItems::Type.default_by_type(:issue).widget_definitions.find_by_widget_type(:notes)
+ WorkItems::Type.default_by_type(:task).widget_definitions.find_by_widget_type(:notes)
.update!(disabled: true)
end
@@ -133,10 +129,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'when body contains quick actions' do
- let_it_be(:noteable) { create(:work_item, :task, project: project) }
-
- let(:variables_extra) { {} }
-
it_behaves_like 'work item supports labels widget updates via quick actions'
it_behaves_like 'work item does not support labels widget updates via quick actions'
it_behaves_like 'work item supports assignee widget updates via quick actions'
@@ -145,6 +137,13 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
it_behaves_like 'work item does not support start and due date widget updates via quick actions'
it_behaves_like 'work item supports type change via quick actions'
end
+
+ context 'when work item is directly associated with a group' do
+ let_it_be_with_reload(:group_work_item) { create(:work_item, :group_level, :task, namespace: group) }
+ let(:noteable) { group_work_item }
+
+ it_behaves_like 'a Note mutation that creates a Note'
+ end
end
end
@@ -152,10 +151,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
let(:head_sha) { noteable.diff_head_sha }
let(:body) { '/merge' }
- before do
- project.add_developer(current_user)
- end
-
# NOTE: Known issue https://gitlab.com/gitlab-org/gitlab/-/issues/346557
it 'returns a nil note and info about the command in errors' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index aa7120e482a..64e010aa50f 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1400,14 +1400,13 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
it 'disallows creating a project with an import_url that is not reachable' do
url = 'http://example.com'
endpoint_url = "#{url}/info/refs?service=git-upload-pack"
- error_response = { status: 301, body: '', headers: nil }
- stub_full_request(endpoint_url, method: :get).to_return(error_response)
+ stub_full_request(endpoint_url, method: :get).to_return({ status: 301, body: '', headers: nil })
project_params = { import_url: url, path: 'path-project-Foo', name: 'Foo Project' }
expect { post api(path, user), params: project_params }.not_to change { Project.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response['message']).to eq("#{url} endpoint error: #{error_response[:status]}")
+ expect(json_response['message']).to eq("#{url} is not a valid HTTP Git repository")
end
it 'creates a project with an import_url that is valid' do
diff --git a/spec/rubocop/cop/migration/prevent_index_creation_spec.rb b/spec/rubocop/cop/migration/prevent_index_creation_spec.rb
index 088edfedfc9..fca0c317412 100644
--- a/spec/rubocop/cop/migration/prevent_index_creation_spec.rb
+++ b/spec/rubocop/cop/migration/prevent_index_creation_spec.rb
@@ -4,7 +4,7 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/prevent_index_creation'
RSpec.describe RuboCop::Cop::Migration::PreventIndexCreation do
- let(:forbidden_tables) { %w(ci_builds namespaces) }
+ let(:forbidden_tables) { %w(ci_builds namespaces projects users) }
let(:forbidden_tables_list) { forbidden_tables.join(', ') }
context 'when in migration' do
diff --git a/spec/services/import/validate_remote_git_endpoint_service_spec.rb b/spec/services/import/validate_remote_git_endpoint_service_spec.rb
index 15e80f2c85d..1d2b3975832 100644
--- a/spec/services/import/validate_remote_git_endpoint_service_spec.rb
+++ b/spec/services/import/validate_remote_git_endpoint_service_spec.rb
@@ -7,9 +7,7 @@ RSpec.describe Import::ValidateRemoteGitEndpointService, feature_category: :impo
let_it_be(:base_url) { 'http://demo.host/path' }
let_it_be(:endpoint_url) { "#{base_url}/info/refs?service=git-upload-pack" }
- let_it_be(:endpoint_error_message) { "#{base_url} endpoint error:" }
- let_it_be(:body_error_message) { described_class::INVALID_BODY_MESSAGE }
- let_it_be(:content_type_error_message) { described_class::INVALID_CONTENT_TYPE_MESSAGE }
+ let_it_be(:error_message) { "#{base_url} is not a valid HTTP Git repository" }
describe '#execute' do
let(:valid_response) do
@@ -72,14 +70,13 @@ RSpec.describe Import::ValidateRemoteGitEndpointService, feature_category: :impo
end
it 'reports error when status code is not 200' do
- error_response = { status: 401 }
- stub_full_request(endpoint_url, method: :get).to_return(error_response)
+ stub_full_request(endpoint_url, method: :get).to_return(valid_response.merge({ status: 301 }))
result = subject.execute
expect(result).to be_a(ServiceResponse)
expect(result.error?).to be(true)
- expect(result.message).to eq("#{endpoint_error_message} #{error_response[:status]}")
+ expect(result.message).to eq(error_message)
end
it 'reports error when invalid URL is provided' do
@@ -97,49 +94,27 @@ RSpec.describe Import::ValidateRemoteGitEndpointService, feature_category: :impo
expect(result).to be_a(ServiceResponse)
expect(result.error?).to be(true)
- expect(result.message).to eq(content_type_error_message)
- end
-
- it 'reports error when body is too short' do
- stub_full_request(endpoint_url, method: :get).to_return(valid_response.merge({ body: 'invalid content' }))
-
- result = subject.execute
-
- expect(result).to be_a(ServiceResponse)
- expect(result.error?).to be(true)
- expect(result.message).to eq(body_error_message)
+ expect(result.message).to eq(error_message)
end
it 'reports error when body is in invalid format' do
- stub_full_request(endpoint_url, method: :get).to_return(valid_response.merge({ body: 'invalid long content with no git respons whatshowever' }))
-
- result = subject.execute
-
- expect(result).to be_a(ServiceResponse)
- expect(result.error?).to be(true)
- expect(result.message).to eq(body_error_message)
- end
-
- it 'reports error when http exceptions are raised' do
- err = SocketError.new('dummy message')
- stub_full_request(endpoint_url, method: :get).to_raise(err)
+ stub_full_request(endpoint_url, method: :get).to_return(valid_response.merge({ body: 'invalid content' }))
result = subject.execute
expect(result).to be_a(ServiceResponse)
expect(result.error?).to be(true)
- expect(result.message).to eq("HTTP #{err.class.name.underscore} error: #{err.message}")
+ expect(result.message).to eq(error_message)
end
- it 'reports error when other exceptions are raised' do
- err = StandardError.new('internal dummy message')
- stub_full_request(endpoint_url, method: :get).to_raise(err)
+ it 'reports error when exception is raised' do
+ stub_full_request(endpoint_url, method: :get).to_raise(SocketError.new('dummy message'))
result = subject.execute
expect(result).to be_a(ServiceResponse)
expect(result.error?).to be(true)
- expect(result.message).to eq("Internal #{err.class.name.underscore} error: #{err.message}")
+ expect(result.message).to eq(error_message)
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 0cc66696184..87491515053 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Notes::CreateService, feature_category: :team_planning do
- let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
@@ -13,11 +14,43 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do
describe '#execute' do
subject(:note) { described_class.new(project, user, opts).execute }
- before do
- project.add_maintainer(user)
+ before_all do
+ group.add_maintainer(user)
end
context "valid params" do
+ context 'when noteable is an issue that belongs directly to a group' do
+ it 'creates a note without a project and correct namespace', :aggregate_failures do
+ group_issue = create(:issue, :group_level, namespace: group)
+ note_params = { note: 'test note', noteable: group_issue }
+
+ expect do
+ described_class.new(nil, user, note_params).execute
+ end.to change { Note.count }.by(1)
+
+ created_note = Note.last
+
+ expect(created_note.namespace).to eq(group)
+ expect(created_note.project).to be_nil
+ end
+ end
+
+ context 'when noteable is a work item that belongs directly to a group' do
+ it 'creates a note without a project and correct namespace', :aggregate_failures do
+ group_work_item = create(:work_item, :group_level, namespace: group)
+ note_params = { note: 'test note', noteable: group_work_item }
+
+ expect do
+ described_class.new(nil, user, note_params).execute
+ end.to change { Note.count }.by(1)
+
+ created_note = Note.last
+
+ expect(created_note.namespace).to eq(group)
+ expect(created_note.project).to be_nil
+ end
+ end
+
it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
let(:action) { note }
end
diff --git a/spec/support/helpers/prevent_set_operator_mismatch_helper.rb b/spec/support/helpers/prevent_set_operator_mismatch_helper.rb
new file mode 100644
index 00000000000..482a5560fe9
--- /dev/null
+++ b/spec/support/helpers/prevent_set_operator_mismatch_helper.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module PreventSetOperatorMismatchHelper
+ extend ActiveSupport::Concern
+
+ included do
+ before do
+ stub_const('Type', Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Type)
+ end
+ end
+
+ def sql_select_node(sql)
+ parsed = PgQuery.parse(sql)
+ parsed.tree.stmts[0].stmt.select_stmt
+ end
+end
diff --git a/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb
index 0577ac329e6..9af9aaef483 100644
--- a/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb
@@ -70,7 +70,7 @@ RSpec.shared_examples 'work item supports labels widget updates via quick action
let(:add_label_ids) { [] }
let(:remove_label_ids) { [] }
- before_all do
+ before do
noteable.update!(labels: [existing_label])
end
diff --git a/yarn.lock b/yarn.lock
index 0505caef0fa..9cbcebc398e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1274,10 +1274,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.66.0.tgz#5dbe98f9811001942d78395756b9d7c588300c01"
integrity sha512-FdkoMAprxjJJnl90GJYoCMeIpvCaYPNAnRkrlsmo7NY3Ce8fpRb/XE/ZakqULeadj82S7R1IRuTHYfWB06vVtA==
-"@gitlab/ui@66.33.0":
- version "66.33.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-66.33.0.tgz#d704a0c919a857f447788b12fede268b605db88d"
- integrity sha512-beKlOILzrokwnFom7c15VMcmndlrmUuEyxywv7E0tgGzDSDy7VwCdbfNbS56CT+VRTxlwQtrvv94jNDivt4NFg==
+"@gitlab/ui@66.34.0":
+ version "66.34.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-66.34.0.tgz#5869589cd067f2b359ef369a6db963820bb9f128"
+ integrity sha512-zoEtq7guKo1OlJlGDJI5K8o3UIpsz8vvghNhX6w1OsVEQ7K2chn+Wgb9zPVGwyAoU48QZAtK8EGgm/FnbPkTlA==
dependencies:
"@floating-ui/dom" "1.2.9"
bootstrap-vue "2.23.1"
@@ -4076,9 +4076,9 @@ camelcase@^6.2.0, camelcase@^6.3.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464:
- version "1.0.30001478"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz#0ef8a1cf8b16be47a0f9fc4ecfc952232724b32a"
- integrity sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==
+ version "1.0.30001549"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz#7d1a3dce7ea78c06ed72c32c2743ea364b3615aa"
+ integrity sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==
canvas-confetti@^1.4.0:
version "1.4.0"