diff options
30 files changed, 440 insertions, 155 deletions
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml index 763941fccf3..81b9544f574 100644 --- a/.rubocop_todo/style/empty_method.yml +++ b/.rubocop_todo/style/empty_method.yml @@ -108,7 +108,6 @@ Style/EmptyMethod: - 'ee/app/controllers/registrations/company_controller.rb' - 'ee/app/controllers/registrations/verification_controller.rb' - 'ee/app/controllers/subscriptions/groups_controller.rb' - - 'ee/app/controllers/trial_registrations_controller.rb' - 'ee/app/controllers/trials_controller.rb' - 'ee/app/controllers/users/identity_verification_controller.rb' - 'ee/app/experiments/cart_abandonment_modal_experiment.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index bd3e8d510ee..dafdd717c19 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -988e9e17374a0ae05a155ce0b459145c389c1524 +f6a9d44f05809aaa59fc9aff9b0465cf3cb7aea9 @@ -197,7 +197,6 @@ gem 'asciidoctor-plantuml', '~> 0.0.16' gem 'asciidoctor-kroki', '~> 0.7.0', require: false gem 'rouge', '~> 3.30.0' gem 'truncato', '~> 0.7.12' -gem 'bootstrap_form', '~> 4.2.0' gem 'nokogiri', '~> 1.14.1' # Calendar rendering diff --git a/Gemfile.lock b/Gemfile.lock index 993092a5e2e..f38f2a2871d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -246,9 +246,6 @@ GEM binding_ninja (0.2.3) bootsnap (1.16.0) msgpack (~> 1.2) - bootstrap_form (4.2.0) - actionpack (>= 5.0) - activemodel (>= 5.0) browser (5.3.1) builder (3.2.4) bullet (7.0.2) @@ -1609,7 +1606,6 @@ DEPENDENCIES benchmark-memory (~> 0.1) better_errors (~> 2.9.1) bootsnap (~> 1.16.0) - bootstrap_form (~> 4.2.0) browser (~> 5.3.1) bullet (~> 7.0.2) bundler-audit (~> 0.7.0.1) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 558cc360342..84182e57e2e 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -62,6 +62,8 @@ class SearchController < ApplicationController @search_highlight = @search_service_presenter.search_highlight end + return if @search_results.respond_to?(:failed?) && @search_results.failed? + Gitlab::Metrics::GlobalSearchSlis.record_apdex( elapsed: @global_search_duration_s, search_type: @search_type, diff --git a/app/services/import_csv/base_service.rb b/app/services/import_csv/base_service.rb new file mode 100644 index 00000000000..fd75a251dd2 --- /dev/null +++ b/app/services/import_csv/base_service.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ImportCsv + class BaseService + def initialize(user, project, csv_io) + @user = user + @project = project + @csv_io = csv_io + @results = { success: 0, error_lines: [], parse_error: false } + end + + def execute + process_csv + email_results_to_user + + results + end + + def email_results_to_user + raise NotImplementedError + end + + private + + attr_reader :user, :project, :csv_io, :results + + def attributes_for(row) + raise NotImplementedError + end + + def validate_headers_presence!(headers) + raise NotImplementedError + end + + def create_object_class + raise NotImplementedError + end + + def process_csv + with_csv_lines.each do |row, line_no| + attributes = attributes_for(row) + + if create_object(attributes)&.persisted? + results[:success] += 1 + else + results[:error_lines].push(line_no) + end + end + rescue ArgumentError, CSV::MalformedCSVError + results[:parse_error] = true + end + + def with_csv_lines + csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8) + validate_headers_presence!(csv_data.lines.first) + + CSV.new( + csv_data, + col_sep: detect_col_sep(csv_data.lines.first), + headers: true, + header_converters: :symbol + ).each.with_index(2) + end + + def detect_col_sep(header) + if header.include?(",") + "," + elsif header.include?(";") + ";" + elsif header.include?("\t") + "\t" + else + raise CSV::MalformedCSVError.new('Invalid CSV format', 1) + end + end + + def create_object(attributes) + # NOTE: CSV imports are performed by workers, so we do not have a request context in order + # to create a SpamParams object to pass to the issuable create service. + spam_params = nil + + # default_params can be extracted into a method if we need + # to support creation of objects that belongs to groups. + default_params = { project: project, + current_user: user, + params: attributes, + spam_params: spam_params } + + create_service = create_object_class.new(**default_params.merge(extra_create_service_params)) + + create_service.execute_without_rate_limiting + end + + # Overidden in subclasses to support specific parameters + def extra_create_service_params + {} + end + end +end diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb index e84d1032e41..83cf5a67453 100644 --- a/app/services/issuable/import_csv/base_service.rb +++ b/app/services/issuable/import_csv/base_service.rb @@ -2,38 +2,13 @@ module Issuable module ImportCsv - class BaseService - def initialize(user, project, csv_io) - @user = user - @project = project - @csv_io = csv_io - @results = { success: 0, error_lines: [], parse_error: false } - end - - def execute - process_csv - email_results_to_user - - @results - end + class BaseService < ::ImportCsv::BaseService + extend ::Gitlab::Utils::Override private - def process_csv - with_csv_lines.each do |row, line_no| - attributes = issuable_attributes_for(row) - - if create_issuable(attributes)&.persisted? - @results[:success] += 1 - else - @results[:error_lines].push(line_no) - end - end - rescue ArgumentError, CSV::MalformedCSVError - @results[:parse_error] = true - end - - def issuable_attributes_for(row) + override :attributes_for + def attributes_for(row) { title: row[:title], description: row[:description], @@ -41,58 +16,13 @@ module Issuable } end - def with_csv_lines - csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8) - validate_headers_presence!(csv_data.lines.first) - - CSV.new( - csv_data, - col_sep: detect_col_sep(csv_data.lines.first), - headers: true, - header_converters: :symbol - ).each.with_index(2) - end - + override :validate_headers_presence! def validate_headers_presence!(headers) headers.downcase! if headers return if headers && headers.include?('title') && headers.include?('description') raise CSV::MalformedCSVError end - - def detect_col_sep(header) - if header.include?(",") - "," - elsif header.include?(";") - ";" - elsif header.include?("\t") - "\t" - else - raise CSV::MalformedCSVError - end - end - - def create_issuable(attributes) - # NOTE: CSV imports are performed by workers, so we do not have a request context in order - # to create a SpamParams object to pass to the issuable create service. - spam_params = nil - create_service = create_issuable_class.new(project: @project, current_user: @user, params: attributes, spam_params: spam_params) - - # For now, if create_issuable_class prepends RateLimitedService let's bypass rate limiting - if create_issuable_class < RateLimitedService - create_service.execute_without_rate_limiting - else - create_service.execute - end - end - - def email_results_to_user - # defined in ImportCsvService - end - - def create_issuable_class - # defined in ImportCsvService - end end end end diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb index 83e550583f6..c3d6af952b4 100644 --- a/app/services/issues/import_csv_service.rb +++ b/app/services/issues/import_csv_service.rb @@ -9,21 +9,21 @@ module Issues end def email_results_to_user - Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_later + Notify.import_issues_csv_email(user.id, project.id, results).deliver_later end private - def create_issuable(attributes) + def create_object(attributes) super[:issue] end - def create_issuable_class + def create_object_class Issues::CreateService end def record_import_attempt - Issues::CsvImport.create!(user: @user, project: @project) + Issues::CsvImport.create!(user: user, project: project) end end end diff --git a/app/services/jira_connect_installations/update_service.rb b/app/services/jira_connect_installations/update_service.rb index b2b6f2a91f2..ff5b9671e2b 100644 --- a/app/services/jira_connect_installations/update_service.rb +++ b/app/services/jira_connect_installations/update_service.rb @@ -24,7 +24,7 @@ module JiraConnectInstallations end end - send_uninstalled_hook if instance_url_changed? + send_uninstalled_hook if instance_url_changed? && @installation.instance_url.blank? ServiceResponse.new(status: :success) end diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml index 195f0f3ad8a..7a57b5cc0fc 100644 --- a/app/views/search/_results_list.html.haml +++ b/app/views/search/_results_list.html.haml @@ -1,5 +1,7 @@ - if @timeout = render partial: "search/results/timeout" +- elsif @search_results.respond_to?(:failed?) && @search_results.failed? + = render partial: "search/results/error" - elsif @search_objects.to_a.empty? = render partial: "search/results/empty" - else diff --git a/app/views/search/results/_error.html.haml b/app/views/search/results/_error.html.haml new file mode 100644 index 00000000000..f0d9283c620 --- /dev/null +++ b/app/views/search/results/_error.html.haml @@ -0,0 +1,7 @@ +.gl-display-flex.gl-flex-direction-column.gl-align-items-center + %div + .svg-content.svg-150 + = image_tag 'illustrations/search-timeout-md.svg' + %div + %h4.gl-text-center.gl-font-weight-bold= s_('SearchError|A search query problem has occurred') + %p.gl-text-center= s_('SearchError|To resolve the problem, check the query syntax and try again.') diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 1727fabebd1..04103794e60 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -8,7 +8,7 @@ - group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name) - project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace) -- if @search_results +- if @search_results && !(@search_results.respond_to?(:failed?) && @search_results.failed?) - if @search_service_presenter.without_count? - page_description(_("%{scope} results for term '%{term}'") % { scope: @scope, term: @search_term }) - else diff --git a/config/feature_flags/development/gitlab_pat_auto_revocation.yml b/config/feature_flags/development/gitlab_pat_auto_revocation.yml deleted file mode 100644 index 3bbbadac23f..00000000000 --- a/config/feature_flags/development/gitlab_pat_auto_revocation.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: gitlab_pat_auto_revocation -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103713 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382610 -milestone: '15.6' -type: development -group: group::static analysis -default_enabled: false diff --git a/config/feature_flags/development/use_response_url_for_chat_responder.yml b/config/feature_flags/development/use_response_url_for_chat_responder.yml new file mode 100644 index 00000000000..84ac2a27fab --- /dev/null +++ b/config/feature_flags/development/use_response_url_for_chat_responder.yml @@ -0,0 +1,8 @@ +--- +name: use_response_url_for_chat_responder +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110625 +rollout_issue_url: +milestone: '15.9' +type: development +group: group::integrations +default_enabled: false diff --git a/config/initializers/bootstrap_form.rb b/config/initializers/bootstrap_form.rb deleted file mode 100644 index 8121bc8bf1d..00000000000 --- a/config/initializers/bootstrap_form.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module BootstrapFormBuilderCustomization - def label_class - "label-bold" - end -end - -BootstrapForm::FormBuilder.prepend(BootstrapFormBuilderCustomization) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 4eab45a67fb..a16a8c0e662 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5029,6 +5029,25 @@ Input type: `SecurityFindingDismissInput` | <a id="mutationsecurityfindingdismisserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationsecurityfindingdismissuuid"></a>`uuid` | [`String`](#string) | UUID of dismissed finding. | +### `Mutation.securityFindingRevertToDetected` + +Input type: `SecurityFindingRevertToDetectedInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationsecurityfindingreverttodetectedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationsecurityfindingreverttodetecteduuid"></a>`uuid` | [`String!`](#string) | UUID of the finding to be dismissed. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationsecurityfindingreverttodetectedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationsecurityfindingreverttodetectederrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| <a id="mutationsecurityfindingreverttodetectedsecurityfinding"></a>`securityFinding` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | Finding reverted to detected. | + ### `Mutation.securityPolicyProjectAssign` Assigns the specified project(`security_policy_project_id`) as security policy project for the given project(`full_path`). If the project already has a security policy project, this reassigns the project's security policy project with the given `security_policy_project_id`. diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 016a5b42086..d316ed7a9de 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -65,6 +65,64 @@ Different features are available in different [GitLab tiers](https://about.gitla | [Access the Security Dashboard](../security_dashboard/index.md) | **{dotted-circle}** No | **{check-circle}** Yes | | [Customize Secret Detection rulesets](#custom-rulesets) | **{dotted-circle}** No | **{check-circle}** Yes | +## Coverage + +Secret Detection scans different aspects of your code, depending on the situation. For all methods +except "Default branch", Secret Detection scans commits, not the working tree. For example, +Secret Detection can detect if a secret was added in one commit and removed in a later commit. + +- Historical scan + + If the `SECRET_DETECTION_HISTORIC_SCAN` variable is set, the content of all + [branches](../../project/repository/branches/index.md) is scanned. Before scanning the + repository's content, Secret Detection runs the command `git fetch --all` to fetch the content of all + branches. + +- Commit range + + If the `SECRET_DETECTION_LOG_OPTS` variable is set, the secrets analyzer fetches the entire + history of the branch or reference the pipeline is being run for. Secret Detection then runs, + scanning the commit range specified. + +- Default branch + + When Secret Detection is run on the default branch, the Git repository is treated as a plain + folder. Only the contents of the repository at the current HEAD are scanned. Commit history is not scanned. + +- Push event + + On a push event, Secret Detection determines what commit range to scan, given the information + available in the runner. To determine the commit range, the variables `CI_COMMIT_SHA` and + `CI_COMMIT_BEFORE_SHA` are important. + + - `CI_COMMIT_SHA` is the commit at HEAD for a given branch. This variable is always set for push events. + - `CI_COMMIT_BEFORE_SHA` is set in most cases. However, it is not set for the first push event on + a new branch, nor for merge pipelines. Because of this, Secret Detection can't be guaranteed + when multiple commits are committed to a new branch. + +- Merge request + + In a merge request, Secret Detection scans every commit made on the source branch. To use this + feature, you must use the [`latest` Secret Detection template](#templates), as it supports + [merge request pipelines](../../../ci/pipelines/merge_request_pipelines.md). + +## Templates + +Secret Detection default configuration is defined in CI/CD templates. Updates to the template are +provided with GitLab upgrades, allowing you to benefit from any improvements and additions. + +Available templates: + +- [`Secret-Detection.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml): Stable version of the Secret Detection CI/CD template. +- [`Secret-Detection.latest.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml): Latest version of the Secret Detection template. + +WARNING: +The latest version of the template may include breaking changes. Use the stable template unless you +need a feature provided only in the latest template. + +For more information about template versioning, see the +[CI/CD documentation](../../../development/cicd/templates.md#latest-version). + ## Enable Secret Detection Prerequisites: diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md index f918c145552..6f74a10f479 100644 --- a/doc/user/application_security/secret_detection/post_processing.md +++ b/doc/user/application_security/secret_detection/post_processing.md @@ -8,6 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6. > - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only. +> - [Enabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.9 FLAG: By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com diff --git a/doc/user/infrastructure/iac/terraform_template_recipes.md b/doc/user/infrastructure/iac/terraform_template_recipes.md index 894c53c0f46..89a97f305e4 100644 --- a/doc/user/infrastructure/iac/terraform_template_recipes.md +++ b/doc/user/infrastructure/iac/terraform_template_recipes.md @@ -29,6 +29,69 @@ The `destroy` job is part of the `cleanup` stage. Like the `deploy` job, the `destroy` job is always `manual` and is not tied to the default branch. +To connect the `destroy` job to the GitLab environment: + +```yaml +include: + - template: Terraform.latest.gitlab-ci.yml + +deploy: + envrionment: + name: $TF_STATE_NAME + action: start + on_stop: destroy + +destroy: + extends: .terraform:destroy + environment: + name: $TF_STATE_NAME + action: stop +``` + +In this configuration, the `destroy` job is always created. However, you might want to create a `destroy` job only if certain +conditions are met. + +The following configuration creates a `destroy` job, runs a destroy plan and omits the `deploy` job only if `TF_DESTROY` is true: + +```yaml +include: + - template: Terraform.latest.gitlab-ci.yml + +build: + rules: + - if: $TF_DESTROY == "true" + variables: + TF_CLI_ARGS_plan: "-destroy" + - when: on_success + +deploy: + envrionment: + name: $TF_STATE_NAME + action: start + on_stop: destroy + rules: + - if: $TF_DESTROY == "true" + when: never + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_AUTO_DEPLOY == "true" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + when: manual + +destroy: + extends: .terraform:destroy + dependencies: + - build + variables: + TF_CLI_ARGS_destroy: "${TF_PLAN_CACHE}" + environment: + name: $TF_STATE_NAME + action: stop + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == "true" + when: manual +``` + +This configuration has a known issue: when the `destroy` job is not in the same pipeline as the `deploy` job, the `on_stop` environment action does not work. + ## Run a custom `terraform` command in a job To define a job that runs a custom `terraform` command, the diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index b0754e74314..da53fe2f5e9 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -10,6 +10,14 @@ In GitLab, [permissions](../permissions.md) are fundamentally defined around the idea of having read or write permission to the repository and branches. To impose further restrictions on certain branches, they can be protected. +A protected branch controls: + +- Which users can merge into the branch. +- Which users can push to the branch. +- If users can force push to the branch. +- If changes to files listed in the CODEOWNERS file can be pushed directly to the branch. +- Which users can unprotect the branch. + The [default branch](repository/branches/default.md) for your repository is protected by default. ## Who can modify a protected branch diff --git a/lib/gitlab/chat/responder.rb b/lib/gitlab/chat/responder.rb index 53a625e9d43..478be5bd350 100644 --- a/lib/gitlab/chat/responder.rb +++ b/lib/gitlab/chat/responder.rb @@ -11,10 +11,21 @@ module Gitlab # # build - A `Ci::Build` that executed a chat command. def self.responder_for(build) - integration = build.pipeline.chat_data&.chat_name&.integration + if Feature.enabled?(:use_response_url_for_chat_responder) + response_url = build.pipeline.chat_data&.response_url + return unless response_url - if (responder = integration.try(:chat_responder)) - responder.new(build) + if response_url.start_with?('https://hooks.slack.com/') + Gitlab::Chat::Responder::Slack.new(build) + else + Gitlab::Chat::Responder::Mattermost.new(build) + end + else + integration = build.pipeline.chat_data&.chat_name&.integration + + if (responder = integration.try(:chat_responder)) + responder.new(build) + end end end end diff --git a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml index 0b6c10293fc..f3f736e96c4 100644 --- a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml @@ -24,6 +24,9 @@ validate: build: extends: .terraform:build + environment: + name: $TF_STATE_NAME + action: prepare deploy: extends: .terraform:deploy @@ -31,3 +34,4 @@ deploy: - build environment: name: $TF_STATE_NAME + action: start diff --git a/lib/gitlab/database/bulk_update.rb b/lib/gitlab/database/bulk_update.rb index 4b4a9b38fd8..36dbb157b0d 100644 --- a/lib/gitlab/database/bulk_update.rb +++ b/lib/gitlab/database/bulk_update.rb @@ -43,15 +43,7 @@ module Gitlab end def update! - if without_prepared_statement? - # A workaround for https://github.com/rails/rails/issues/24893 - # When prepared statements are prevented (such as when using the - # query counter or in omnibus by default), we cannot call - # `exec_update`, since that will discard the bindings. - connection.send(:exec_no_cache, sql, log_name, params) # rubocop: disable GitlabSecurity/PublicSend - else - connection.exec_update(sql, log_name, params) - end + connection.exec_update(sql, log_name, params) end def self.column_definitions(model, columns) @@ -93,14 +85,6 @@ module Gitlab end end - # A workaround for https://github.com/rails/rails/issues/24893 - # We need to detect if prepared statements have been disabled. - def without_prepared_statement? - strong_memoize(:without_prepared_statement) do - connection.send(:without_prepared_statement?, [1]) # rubocop: disable GitlabSecurity/PublicSend - end - end - def query_attribute(column, key, values) value = values[column.name] key[column.name] = value if key.try(:id) # optimistic update diff --git a/lib/gitlab/database/load_balancing/connection_proxy.rb b/lib/gitlab/database/load_balancing/connection_proxy.rb index f0343f9d8b5..622e310ead3 100644 --- a/lib/gitlab/database/load_balancing/connection_proxy.rb +++ b/lib/gitlab/database/load_balancing/connection_proxy.rb @@ -97,11 +97,11 @@ module Gitlab if current_session.use_primary? && !current_session.use_replicas_for_read_queries? @load_balancer.read_write do |connection| - connection.send(...) + connection.public_send(...) end else @load_balancer.read do |connection| - connection.send(...) + connection.public_send(...) end end end @@ -117,7 +117,7 @@ module Gitlab end @load_balancer.read_write do |connection| - connection.send(...) + connection.public_send(...) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 590bebb49bd..6f4459ccc3a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37414,6 +37414,12 @@ msgstr "" msgid "SearchCodeResults|of %{link_to_project}" msgstr "" +msgid "SearchError|A search query problem has occurred" +msgstr "" + +msgid "SearchError|To resolve the problem, check the query syntax and try again." +msgstr "" + msgid "SearchResults|Showing %{count} %{scope} for %{term_element}" msgstr "" diff --git a/spec/initializers/0_postgresql_types_spec.rb b/spec/initializers/0_postgresql_types_spec.rb index 76b243033d0..99f9b76a34e 100644 --- a/spec/initializers/0_postgresql_types_spec.rb +++ b/spec/initializers/0_postgresql_types_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'PostgreSQL registered types' do - subject(:types) { ApplicationRecord.connection.send(:type_map).keys } + subject(:types) { ApplicationRecord.connection.reload_type_map.keys } # These can be obtained via SELECT oid, typname from pg_type it 'includes custom and standard OIDs' do diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb index 803f30da9e7..a9d290cb87c 100644 --- a/spec/lib/gitlab/chat/responder_spec.rb +++ b/spec/lib/gitlab/chat/responder_spec.rb @@ -2,30 +2,70 @@ require 'spec_helper' -RSpec.describe Gitlab::Chat::Responder do +RSpec.describe Gitlab::Chat::Responder, feature_category: :integrations do describe '.responder_for' do - context 'using a regular build' do - it 'returns nil' do - build = create(:ci_build) + context 'when the feature flag is disabled' do + before do + stub_feature_flags(use_response_url_for_chat_responder: false) + end + + context 'using a regular build' do + it 'returns nil' do + build = create(:ci_build) + + expect(described_class.responder_for(build)).to be_nil + end + end + + context 'using a chat build' do + it 'returns the responder for the build' do + pipeline = create(:ci_pipeline) + build = create(:ci_build, pipeline: pipeline) + integration = double(:integration, chat_responder: Gitlab::Chat::Responder::Slack) + chat_name = double(:chat_name, integration: integration) + chat_data = double(:chat_data, chat_name: chat_name) + + allow(pipeline) + .to receive(:chat_data) + .and_return(chat_data) - expect(described_class.responder_for(build)).to be_nil + expect(described_class.responder_for(build)) + .to be_an_instance_of(Gitlab::Chat::Responder::Slack) + end end end - context 'using a chat build' do - it 'returns the responder for the build' do - pipeline = create(:ci_pipeline) - build = create(:ci_build, pipeline: pipeline) - integration = double(:integration, chat_responder: Gitlab::Chat::Responder::Slack) - chat_name = double(:chat_name, integration: integration) - chat_data = double(:chat_data, chat_name: chat_name) + context 'when the feature flag is enabled' do + before do + stub_feature_flags(use_response_url_for_chat_responder: true) + end + + context 'using a regular build' do + it 'returns nil' do + build = create(:ci_build) + + expect(described_class.responder_for(build)).to be_nil + end + end + + context 'using a chat build' do + let(:chat_name) { create(:chat_name, chat_id: 'U123') } + let(:pipeline) do + pipeline = create(:ci_pipeline) + pipeline.create_chat_data!( + response_url: 'https://hooks.slack.com/services/12345', + chat_name_id: chat_name.id + ) + pipeline + end - allow(pipeline) - .to receive(:chat_data) - .and_return(chat_data) + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:responder) { described_class.new(build) } - expect(described_class.responder_for(build)) - .to be_an_instance_of(Gitlab::Chat::Responder::Slack) + it 'returns the responder for the build' do + expect(described_class.responder_for(build)) + .to be_an_instance_of(Gitlab::Chat::Responder::Slack) + end end end end diff --git a/spec/services/import_csv/base_service_spec.rb b/spec/services/import_csv/base_service_spec.rb new file mode 100644 index 00000000000..0c0ed40ff4d --- /dev/null +++ b/spec/services/import_csv/base_service_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ImportCsv::BaseService, feature_category: :importers do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:csv_io) { double } + + subject { described_class.new(user, project, csv_io) } + + shared_examples 'abstract method' do |method, args| + it "raises NotImplemented error when #{method} is called" do + if args + expect { subject.send(method, args) }.to raise_error(NotImplementedError) + else + expect { subject.send(method) }.to raise_error(NotImplementedError) + end + end + end + + it_behaves_like 'abstract method', :email_results_to_user + it_behaves_like 'abstract method', :attributes_for, "any" + it_behaves_like 'abstract method', :validate_headers_presence!, "any" + it_behaves_like 'abstract method', :create_object_class + + describe '#detect_col_sep' do + context 'when header contains invalid separators' do + it 'raises error' do + header = 'Name&email' + + expect { subject.send(:detect_col_sep, header) }.to raise_error(CSV::MalformedCSVError) + end + end + + context 'when header is valid' do + shared_examples 'header with valid separators' do + let(:header) { "Name#{separator}email" } + + it 'returns separator value' do + expect(subject.send(:detect_col_sep, header)).to eq(separator) + end + end + + context 'with ; as separator' do + let(:separator) { ';' } + + it_behaves_like 'header with valid separators' + end + + context 'with \t as separator' do + let(:separator) { "\t" } + + it_behaves_like 'header with valid separators' + end + + context 'with , as separator' do + let(:separator) { ',' } + + it_behaves_like 'header with valid separators' + end + end + end +end diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb index 9ad1d7dba9f..90e360f9cf1 100644 --- a/spec/services/issues/import_csv_service_spec.rb +++ b/spec/services/issues/import_csv_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ImportCsvService do +RSpec.describe Issues::ImportCsvService, feature_category: :team_planning do let(:project) { create(:project) } let(:user) { create(:user) } let(:assignee) { create(:user, username: 'csv_assignee') } diff --git a/spec/services/jira_connect_installations/update_service_spec.rb b/spec/services/jira_connect_installations/update_service_spec.rb index ec5bb5d6d6a..15f3b485b20 100644 --- a/spec/services/jira_connect_installations/update_service_spec.rb +++ b/spec/services/jira_connect_installations/update_service_spec.rb @@ -45,8 +45,9 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ let_it_be_with_reload(:installation) { create(:jira_connect_installation, instance_url: 'https://other_gitlab.example.com') } it 'sends an installed event to the instance', :aggregate_failures do - expect_next_instance_of(JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, -'https://other_gitlab.example.com') do |proxy_lifecycle_events_service| + expect_next_instance_of( + JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, 'https://other_gitlab.example.com' + ) do |proxy_lifecycle_events_service| expect(proxy_lifecycle_events_service).to receive(:execute).and_return(ServiceResponse.new(status: :success)) end @@ -62,19 +63,19 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ stub_request(:post, 'https://other_gitlab.example.com/-/jira_connect/events/uninstalled') end - it 'starts an async worker to send an uninstalled event to the previous instance' do - expect(JiraConnect::SendUninstalledHookWorker).to receive(:perform_async).with(installation.id, 'https://other_gitlab.example.com') - + it 'sends an installed event to the instance and updates instance_url' do expect(JiraConnectInstallations::ProxyLifecycleEventService) .to receive(:execute).with(installation, :installed, 'https://gitlab.example.com') .and_return(ServiceResponse.new(status: :success)) + expect(JiraConnect::SendUninstalledHookWorker).not_to receive(:perform_async) + execute_service expect(installation.instance_url).to eq(update_params[:instance_url]) end - context 'and the new instance_url is empty' do + context 'and the new instance_url is nil' do let(:update_params) { { instance_url: nil } } it 'starts an async worker to send an uninstalled event to the previous instance' do @@ -98,8 +99,9 @@ RSpec.describe JiraConnectInstallations::UpdateService, feature_category: :integ let(:update_params) { { instance_url: 'https://gitlab.example.com' } } it 'sends an installed event to the instance and updates instance_url' do - expect_next_instance_of(JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, -'https://gitlab.example.com') do |proxy_lifecycle_events_service| + expect_next_instance_of( + JiraConnectInstallations::ProxyLifecycleEventService, installation, :installed, 'https://gitlab.example.com' + ) do |proxy_lifecycle_events_service| expect(proxy_lifecycle_events_service).to receive(:execute).and_return(ServiceResponse.new(status: :success)) end |