diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 300706a4d8e..43eb86942f9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -160,7 +160,6 @@ Lint/MixedRegexpCaptureTypes: - 'lib/gitlab/diff/suggestions_parser.rb' - 'lib/gitlab/github_import/representation/note.rb' - 'lib/gitlab/metrics/system.rb' - - 'lib/gitlab/request_profiler/profile.rb' - 'lib/gitlab/slash_commands/issue_move.rb' - 'lib/gitlab/slash_commands/issue_new.rb' - 'lib/gitlab/slash_commands/run.rb' diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml index 40afa0d0667..6858dcc36ce 100644 --- a/.rubocop_todo/gitlab/namespaced_class.yml +++ b/.rubocop_todo/gitlab/namespaced_class.yml @@ -875,7 +875,6 @@ Gitlab/NamespacedClass: - app/workers/repository_import_worker.rb - app/workers/repository_remove_remote_worker.rb - app/workers/repository_update_remote_mirror_worker.rb - - app/workers/requests_profiles_worker.rb - app/workers/run_pipeline_schedule_worker.rb - app/workers/schedule_merge_request_cleanup_refs_worker.rb - app/workers/schedule_migrate_external_diffs_worker.rb diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 70d81077c87..5d3c635a654 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -7a8fd30510a92d436a5144c3c20f6654ff3ec51d +66a4a9452e0ee27a29dd36fffe98ea04dab8ae24 diff --git a/Gemfile b/Gemfile index 2d734e0b510..6c2f049f231 100644 --- a/Gemfile +++ b/Gemfile @@ -195,7 +195,7 @@ gem 'state_machines-activerecord', '~> 0.8.0' gem 'acts-as-taggable-on', '~> 9.0' # Background jobs -gem 'sidekiq', '~> 6.3' +gem 'sidekiq', '~> 6.4' gem 'sidekiq-cron', '~> 1.2' gem 'redis-namespace', '~> 1.8.1' gem 'gitlab-sidekiq-fetcher', '0.8.0', require: 'sidekiq-reliable-fetch' diff --git a/Gemfile.lock b/Gemfile.lock index 2dee6391d82..ba7357c3d54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -201,7 +201,7 @@ GEM colored2 (3.1.2) commonmarker (0.23.2) concurrent-ruby (1.1.9) - connection_pool (2.2.2) + connection_pool (2.2.5) contracts (0.11.0) cork (0.3.0) colored2 (~> 3.1) @@ -1174,7 +1174,7 @@ GEM shellany (0.0.1) shoulda-matchers (4.0.1) activesupport (>= 4.2.0) - sidekiq (6.3.1) + sidekiq (6.4.0) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -1620,7 +1620,7 @@ DEPENDENCIES sentry-raven (~> 3.1) settingslogic (~> 2.0.9) shoulda-matchers (~> 4.0.1) - sidekiq (~> 6.3) + sidekiq (~> 6.4) sidekiq-cron (~> 1.2) simple_po_parser (~> 1.1.2) simplecov (~> 0.18.5) diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 66d06a3a1b6..5f8df01e181 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -29,7 +29,6 @@ import { MAX_TREE_WIDTH, TREE_HIDE_STATS_WIDTH, MR_TREE_SHOW_KEY, - CENTERED_LIMITED_CONTAINER_CLASSES, ALERT_OVERFLOW_HIDDEN, ALERT_MERGE_CONFLICT, ALERT_COLLAPSED_FILES, @@ -253,13 +252,6 @@ export default { hideFileStats() { return this.treeWidth <= TREE_HIDE_STATS_WIDTH; }, - isLimitedContainer() { - if (this.glFeatures.mrChangesFluidLayout) { - return false; - } - - return !this.renderFileTree && !this.isParallelView && !this.isFluidLayout; - }, isFullChangeset() { return this.startVersion === null && this.latestDiff; }, @@ -395,8 +387,6 @@ export default { this.adjustView(); this.subscribeToEvents(); - this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES; - this.unwatchDiscussions = this.$watch( () => `${this.diffFiles.length}:${this.$store.state.notes.discussions.length}`, () => this.setDiscussions(), @@ -643,10 +633,7 @@ export default {
'.html_safe, codeClose: '
'.html_safe }
-
-- if @profiles.present?
- .gl-mt-3
- - @profiles.each do |path, profiles|
- .card
- .card-header
- %code= path
- %ul.content-list
- - profiles.each do |profile|
- %li
- = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
- admin_requests_profile_path(profile)
-- else
- %p
- = _('No profiles found')
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index f820f911d61..f661d6277d6 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -103,10 +103,6 @@
= link_to admin_health_check_path, title: _('Health Check') do
%span
= _('Health Check')
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: _('Requests Profiles') do
- %span
- = _('Requests Profiles')
- if Gitlab::CurrentSettings.current_application_settings.grafana_enabled?
= nav_link do
= link_to Gitlab::CurrentSettings.current_application_settings.grafana_url, target: '_blank', title: _('Metrics Dashboard'), rel: 'noopener noreferrer' do
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e07a2a6ef1d..39c69d6cf50 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -597,15 +597,6 @@
:weight: 1
:idempotent:
:tags: []
-- :name: cronjob:requests_profiles
- :worker_name: RequestsProfilesWorker
- :feature_category: :source_code_management
- :has_external_dependencies:
- :urgency: :low
- :resource_boundary: :unknown
- :weight: 1
- :idempotent:
- :tags: []
- :name: cronjob:schedule_merge_request_cleanup_refs
:worker_name: ScheduleMergeRequestCleanupRefsWorker
:feature_category: :code_review
diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb
deleted file mode 100644
index e02b63fb621..00000000000
--- a/app/workers/requests_profiles_worker.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-class RequestsProfilesWorker # rubocop:disable Scalability/IdempotentWorker
- include ApplicationWorker
-
- data_consistency :always
-
- # rubocop:disable Scalability/CronWorkerContext
- # This worker does not perform work scoped to a context
- include CronjobQueue
- # rubocop:enable Scalability/CronWorkerContext
-
- feature_category :source_code_management
-
- def perform
- Gitlab::RequestProfiler.remove_all_profiles
- end
-end
diff --git a/config/feature_flags/development/import_redis_increment_by.yml b/config/feature_flags/development/import_redis_increment_by.yml
deleted file mode 100644
index 9932c8e868e..00000000000
--- a/config/feature_flags/development/import_redis_increment_by.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: import_redis_increment_by
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65773
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336226
-milestone: '14.1'
-type: development
-group: group::import
-default_enabled: true
diff --git a/config/feature_flags/development/mr_changes_fluid_layout.yml b/config/feature_flags/development/mr_changes_fluid_layout.yml
deleted file mode 100644
index dcb9dee2ece..00000000000
--- a/config/feature_flags/development/mr_changes_fluid_layout.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: mr_changes_fluid_layout
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70815
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341809
-milestone: '14.4'
-type: development
-group: group::code review
-default_enabled: true
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8244f570a18..4110fa1df0d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -479,9 +479,6 @@ Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'Impor
Settings.cron_jobs['ci_archive_traces_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_archive_traces_cron_worker']['cron'] ||= '17 * * * *'
Settings.cron_jobs['ci_archive_traces_cron_worker']['job_class'] = 'Ci::ArchiveTracesCronWorker'
-Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
-Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
Settings.cron_jobs['remove_expired_members_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_members_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpiredMembersWorker'
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
index 2eb9f53d2a3..deabe2d3caa 100644
--- a/config/initializers/request_profiler.rb
+++ b/config/initializers/request_profiler.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do |config|
- config.middleware.use(Gitlab::RequestProfiler::Middleware)
config.middleware.use(Gitlab::Middleware::Speedscope)
end
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index ed1afc9efa3..da5df8bfdb3 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -100,7 +100,6 @@ namespace :admin do
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
- resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.(html|txt)/ }
resources :projects, only: [:index]
diff --git a/db/post_migrate/20220124180704_remove_projects_ci_builds_metadata_project_id_fk.rb b/db/post_migrate/20220124180704_remove_projects_ci_builds_metadata_project_id_fk.rb
new file mode 100644
index 00000000000..fc9dc2cc6dc
--- /dev/null
+++ b/db/post_migrate/20220124180704_remove_projects_ci_builds_metadata_project_id_fk.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiBuildsMetadataProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return if Gitlab.com? # unsafe migration, skip on GitLab.com due to https://gitlab.com/groups/gitlab-org/-/epics/7249#note_819625526
+ return unless foreign_key_exists?(:ci_builds_metadata, :projects, name: "fk_rails_ffcf702a02")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_builds_metadata IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_builds_metadata, :projects, name: "fk_rails_ffcf702a02")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_builds_metadata, :projects, name: "fk_rails_ffcf702a02", column: :project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/schema_migrations/20220124180704 b/db/schema_migrations/20220124180704
new file mode 100644
index 00000000000..ef59507a2e8
--- /dev/null
+++ b/db/schema_migrations/20220124180704
@@ -0,0 +1 @@
+5b5f47f1d7038518fef71dd8c0758b234bb890be9aab57b78918f7b2dc39bdc4
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b7b5db75e99..e02b257ddfa 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31538,9 +31538,6 @@ ALTER TABLE ONLY resource_label_events
ALTER TABLE ONLY pages_deployment_states
ADD CONSTRAINT fk_rails_ff6ca551a4 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_builds_metadata
- ADD CONSTRAINT fk_rails_ffcf702a02 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY security_orchestration_policy_configurations
ADD CONSTRAINT fk_security_policy_configurations_management_project_id FOREIGN KEY (security_policy_management_project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/administration/geo/secondary_proxy/index.md b/doc/administration/geo/secondary_proxy/index.md
index ebd71757e91..c8dcbb81312 100644
--- a/doc/administration/geo/secondary_proxy/index.md
+++ b/doc/administration/geo/secondary_proxy/index.md
@@ -140,6 +140,18 @@ for details.
To use TLS certificates with Let's Encrypt, you can manually point the domain to one of the Geo sites, generate
the certificate, then copy it to all other sites.
+## Behavior of secondary sites when the primary Geo site is down
+
+Considering that web traffic is proxied to the primary, the behavior of the secondary sites differs when the primary
+site is inaccessible:
+
+- UI and API traffic return the same errors as the primary (or fail if the primary is not accessible at all), since they are proxied.
+- For repositories that already exist on the specific secondary site being accessed, Git read operations still work as expected,
+ including authentication through HTTP(s) or SSH.
+- Git operations for repositories that are not replicated to the secondary site return the same errors
+ as the primary site, since they are proxied.
+- All Git write operations return the same errors as the primary site, since they are proxied.
+
## Features accelerated by secondary Geo sites
Most HTTP traffic sent to a secondary Geo site can be proxied to the primary Geo site. With this architecture,
diff --git a/doc/administration/geo/setup/database.md b/doc/administration/geo/setup/database.md
index 7f4c771c962..ef474007659 100644
--- a/doc/administration/geo/setup/database.md
+++ b/doc/administration/geo/setup/database.md
@@ -893,6 +893,9 @@ Instead, follow the instructions below.
A production-ready and secure setup requires at least three Consul nodes, two
Patroni nodes and one PgBouncer node on the secondary site.
+Because of [omnibus-6587](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6587), Consul can't track multiple
+services, so these need to be different than the nodes used for the Standby Cluster database.
+
Be sure to use [password credentials](../../postgresql/replication_and_failover.md#database-authorization-for-patroni)
and other database best practices.
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 3bedb6b01bd..e985dfec339 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -228,7 +228,11 @@ There is a limit when embedding metrics in GitLab Flavored Markdown (GFM) for pe
- **Max limit**: 100 embeds.
-## Number of webhooks
+## Webhook limits
+
+Also see [Webhook rate limits](#webhook-rate-limit).
+
+### Number of webhooks
To set the maximum number of group or project webhooks for a self-managed installation,
run the following in the [GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
@@ -246,11 +250,33 @@ Plan.default.actual_limits.update!(group_hooks: 100)
Set the limit to `0` to disable it.
-- **Default maximum number of webhooks**: `100` per project, `50` per group
-- **Maximum payload size**: 25 MB
+The default maximum number of webhooks is `100` per project, `50` per group.
For GitLab.com, see the [webhook limits for GitLab.com](../user/gitlab_com/index.md#webhooks).
+### Webhook payload size
+
+The maximum webhook payload size is 25 MB.
+
+### Recursive webhooks
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329743) in GitLab 14.8.
+
+GitLab detects and blocks webhooks that are recursive or that exceed the limit
+of webhooks that can be triggered from other webhooks. This enables GitLab to
+continue to support workflows that use webhooks to call the API non-recursively, or that
+do not trigger an unreasonable number of other webhooks.
+
+Recursion can happen when a webhook is configured to make a call
+to its own GitLab instance (for example, the API). The call then triggers the same
+webhook and creates an infinite loop.
+
+The maximum number of requests to an instance made by a series of webhooks that
+trigger other webhooks is 100. When the limit is reached, GitLab blocks any further
+webhooks that would be triggered by the series.
+
+Blocked recursive webhook calls are logged in `auth.log` with the message `"Recursive webhook blocked from executing"`.
+
## Pull Mirroring Interval
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/237891) in GitLab 13.7.
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d2a5802de16..e9aa93d85f7 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -4886,6 +4886,25 @@ Input type: `UserCalloutCreateInput`
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `userCallout` | [`UserCallout!`](#usercallout) | User callout dismissed. |
+### `Mutation.userPreferencesUpdate`
+
+Input type: `UserPreferencesUpdateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| `userPreferences` | [`UserPreferences`](#userpreferences) | User preferences after mutation. |
+
### `Mutation.vulnerabilityConfirm`
Input type: `VulnerabilityConfirmInput`
@@ -15740,6 +15759,14 @@ fields relate to interactions between the two entities.
| ---- | ---- | ----------- |
| `createSnippet` | [`Boolean!`](#boolean) | Indicates the user can perform `create_snippet` on this resource. |
+### `UserPreferences`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. |
+
### `UserStatus`
#### Fields
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index 2e6e591e4ec..7f84203a498 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -690,6 +690,9 @@ Roles are not the same as [**access levels**](#access-level).
Use lowercase for **runners**. These are the agents that run CI/CD jobs. See also [GitLab Runner](#gitlab-runner) and [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/233529).
+When referring to runners, if you have to specify that the runners are installed on a customer's GitLab instance,
+use **self-managed** rather than **self-hosted**.
+
## (s)
Do not use **(s)** to make a word optionally plural. It can slow down comprehension. For example:
@@ -735,6 +738,10 @@ Instead of:
Use **select** with buttons, links, menu items, and lists. **Select** applies to more devices,
while **click** is more specific to a mouse.
+## self-managed
+
+Use **self-managed** to refer to a customer's installation of GitLab. Do not use **self-hosted**.
+
## Service Desk
Use title case for **Service Desk**.
diff --git a/lib/gitlab/backtrace_cleaner.rb b/lib/gitlab/backtrace_cleaner.rb
index caea05c720d..d2ca2057eb6 100644
--- a/lib/gitlab/backtrace_cleaner.rb
+++ b/lib/gitlab/backtrace_cleaner.rb
@@ -17,7 +17,6 @@ module Gitlab
lib/gitlab/profiler.rb
lib/gitlab/query_limiting/
lib/gitlab/request_context.rb
- lib/gitlab/request_profiler/
lib/gitlab/sidekiq_logging/
lib/gitlab/sidekiq_middleware/
lib/gitlab/sidekiq_status/
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
index f8031eeeb42..185ac8da6d7 100644
--- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -132,6 +132,10 @@ pages_deployments:
- table: ci_builds
column: ci_build_id
on_delete: async_nullify
+ci_builds_metadata:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
terraform_state_versions:
- table: ci_builds
column: ci_build_id
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index aa5ac1e3486..a2fa2c33a39 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -1054,9 +1054,18 @@ module Gitlab
Arel::Nodes::SqlLiteral.new(replace.to_sql)
end
- def remove_foreign_key_if_exists(...)
- if foreign_key_exists?(...)
- remove_foreign_key(...)
+ def remove_foreign_key_if_exists(source, target = nil, **kwargs)
+ reverse_lock_order = kwargs.delete(:reverse_lock_order)
+ return unless foreign_key_exists?(source, target, **kwargs)
+
+ if target && reverse_lock_order && transaction_open?
+ execute("LOCK TABLE #{target}, #{source} IN ACCESS EXCLUSIVE MODE")
+ end
+
+ if target
+ remove_foreign_key(source, target, **kwargs)
+ else
+ remove_foreign_key(source, **kwargs)
end
end
diff --git a/lib/gitlab/github_import/object_counter.rb b/lib/gitlab/github_import/object_counter.rb
index 4c9a8da601f..7ce88280209 100644
--- a/lib/gitlab/github_import/object_counter.rb
+++ b/lib/gitlab/github_import/object_counter.rb
@@ -71,11 +71,7 @@ module Gitlab
add_counter_to_list(project, operation, counter_key)
- if Feature.disabled?(:import_redis_increment_by, default_enabled: :yaml)
- CACHING.increment(counter_key)
- else
- CACHING.increment_by(counter_key, value)
- end
+ CACHING.increment_by(counter_key, value)
end
def add_counter_to_list(project, operation, key)
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index b2179d80a18..bedcc07e196 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -16,7 +16,6 @@ module Gitlab
lib/gitlab/middleware/
ee/lib/gitlab/middleware/
lib/gitlab/performance_bar/
- lib/gitlab/request_profiler/
lib/gitlab/query_limiting/
lib/gitlab/tracing/
lib/gitlab/profiler.rb
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
deleted file mode 100644
index 541d505e735..00000000000
--- a/lib/gitlab/request_profiler.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'fileutils'
-
-module Gitlab
- module RequestProfiler
- PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
-
- def all
- Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path|
- Profile.new(File.basename(path))
- end.select(&:valid?)
- end
- module_function :all # rubocop: disable Style/AccessModifierDeclarations
-
- def find(name)
- file_path = File.join(PROFILES_DIR, name)
- return unless File.exist?(file_path)
-
- Profile.new(name)
- end
- module_function :find # rubocop: disable Style/AccessModifierDeclarations
-
- def profile_token
- Rails.cache.fetch('profile-token') do
- Devise.friendly_token
- end
- end
- module_function :profile_token # rubocop: disable Style/AccessModifierDeclarations
-
- def remove_all_profiles
- FileUtils.rm_rf(PROFILES_DIR)
- end
- module_function :remove_all_profiles # rubocop: disable Style/AccessModifierDeclarations
- end
-end
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
deleted file mode 100644
index acdf8d4541f..00000000000
--- a/lib/gitlab/request_profiler/middleware.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-# frozen_string_literal: true
-
-require 'ruby-prof'
-require 'memory_profiler'
-
-module Gitlab
- module RequestProfiler
- class Middleware
- def initialize(app)
- @app = app
- end
-
- def call(env)
- if profile?(env)
- call_with_profiling(env)
- else
- @app.call(env)
- end
- end
-
- def profile?(env)
- header_token = env['HTTP_X_PROFILE_TOKEN']
- return unless header_token.present?
-
- profile_token = Gitlab::RequestProfiler.profile_token
- return unless profile_token.present?
-
- header_token == profile_token
- end
-
- def call_with_profiling(env)
- case env['HTTP_X_PROFILE_MODE']
- when 'execution', nil
- call_with_call_stack_profiling(env)
- when 'memory'
- call_with_memory_profiling(env)
- else
- raise ActionController::BadRequest, invalid_profile_mode(env)
- end
- end
-
- def invalid_profile_mode(env)
- <<~HEREDOC
- Invalid X-Profile-Mode: #{env['HTTP_X_PROFILE_MODE']}.
- Supported profile mode request header:
- - X-Profile-Mode: execution
- - X-Profile-Mode: memory
- HEREDOC
- end
-
- def call_with_call_stack_profiling(env)
- ret = nil
- report = RubyProf::Profile.profile do
- ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
- @app.call(env)
- end
- end
-
- generate_report(env, 'execution', 'html') do |file|
- printer = RubyProf::CallStackPrinter.new(report)
- printer.print(file)
- end
-
- handle_request_ret(ret)
- end
-
- def call_with_memory_profiling(env)
- ret = nil
- report = MemoryProfiler.report do
- ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
- @app.call(env)
- end
- end
-
- generate_report(env, 'memory', 'txt') do |file|
- report.pretty_print(to_file: file)
- end
-
- handle_request_ret(ret)
- end
-
- def generate_report(env, report_type, extension)
- file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}"\
- "_#{report_type}.#{extension}"
- file_path = "#{PROFILES_DIR}/#{file_name}"
-
- FileUtils.mkdir_p(PROFILES_DIR)
-
- begin
- File.open(file_path, 'wb') do |file|
- yield(file)
- end
- rescue StandardError
- FileUtils.rm(file_path)
- end
- end
-
- def handle_request_ret(ret)
- if ret.is_a?(Array)
- ret
- else
- throw(:warden, ret) # rubocop:disable Cop/BanCatchThrow
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
deleted file mode 100644
index 76c675658b1..00000000000
--- a/lib/gitlab/request_profiler/profile.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module RequestProfiler
- class Profile
- attr_reader :name, :time, :file_path, :request_path, :profile_mode, :type
-
- alias_method :to_param, :name
-
- def initialize(name)
- @name = name
- @file_path = File.join(PROFILES_DIR, name)
-
- set_attributes
- end
-
- def valid?
- @request_path.present?
- end
-
- def content_type
- case type
- when 'html'
- 'text/html'
- when 'txt'
- 'text/plain'
- end
- end
-
- private
-
- def set_attributes
- matches = name.match(/^(?paragraph.
' - end - - it 'renders the data' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(sample_data) - end - end - - context 'when loading TXT profile' do - let(:basename) { "profile_#{Time.current.to_i}_memory.txt" } - - let(:sample_data) do - <<~TXT - Total allocated: 112096396 bytes (1080431 objects) - Total retained: 10312598 bytes (53567 objects) - TXT - end - - it 'renders the data' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(sample_data) - end - end - - context 'when loading PDF profile' do - let(:basename) { "profile_#{Time.current.to_i}_anything.pdf" } - - let(:sample_data) { 'mocked pdf content' } - - it 'fails to render the data' do - expect { subject }.to raise_error(ActionController::UrlGenerationError, /No route matches.*unmatched constraints:/) - end - end - end -end diff --git a/spec/factories/ci/build_metadata.rb b/spec/factories/ci/build_metadata.rb new file mode 100644 index 00000000000..cfc86c4ef4b --- /dev/null +++ b/spec/factories/ci/build_metadata.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :ci_build_metadata, class: 'Ci::BuildMetadata' do + build { association(:ci_build, strategy: :build, metadata: instance) } + end +end diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb deleted file mode 100644 index e92528d431d..00000000000 --- a/spec/features/admin/admin_requests_profiles_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Admin::RequestsProfilesController' do - let(:tmpdir) { Dir.mktmpdir('profiler-test') } - - before do - stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir) - admin = create(:admin) - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - end - - after do - FileUtils.rm_rf(tmpdir) - end - - describe 'GET /admin/requests_profiles' do - it 'shows the current profile token' do - allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new) - - visit admin_requests_profiles_path - - expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}") - end - - context 'when having multiple profiles' do - let(:time1) { 1.hour.ago } - let(:time2) { 2.hours.ago } - - let(:profiles) do - [ - { - request_path: '/gitlab-org/gitlab-foss', - name: "|gitlab-org|gitlab-foss_#{time1.to_i}_execution.html", - created: time1, - profile_mode: 'Execution' - }, - { - request_path: '/gitlab-org/gitlab-foss', - name: "|gitlab-org|gitlab-foss_#{time2.to_i}_execution.html", - created: time2, - profile_mode: 'Execution' - }, - { - request_path: '/gitlab-org/gitlab-foss', - name: "|gitlab-org|gitlab-foss_#{time1.to_i}_memory.html", - created: time1, - profile_mode: 'Memory' - }, - { - request_path: '/gitlab-org/gitlab-foss', - name: "|gitlab-org|gitlab-foss_#{time2.to_i}_memory.html", - created: time2, - profile_mode: 'Memory' - }, - { - request_path: '/gitlab-org/infrastructure', - name: "|gitlab-org|infrastructure_#{time1.to_i}_execution.html", - created: time1, - profile_mode: 'Execution' - }, - { - request_path: '/gitlab-org/infrastructure', - name: "|gitlab-org|infrastructure_#{time2.to_i}_memory.html", - created: time2, - profile_mode: 'Memory' - }, - { - request_path: '/gitlab-org/infrastructure', - name: "|gitlab-org|infrastructure_#{time2.to_i}.html", - created: time2, - profile_mode: 'Unknown' - } - ] - end - - before do - profiles.each do |profile| - FileUtils.touch(File.join(Gitlab::RequestProfiler::PROFILES_DIR, profile[:name])) - end - end - - it 'lists all available profiles' do - visit admin_requests_profiles_path - - profiles.each do |profile| - within('.card', text: profile[:request_path]) do - expect(page).to have_selector( - "a[href='#{admin_requests_profile_path(profile[:name])}']", - text: "#{profile[:created].to_s(:long)} #{profile[:profile_mode]}") - end - end - end - end - end - - describe 'GET /admin/requests_profiles/:profile' do - context 'when a profile exists' do - before do - File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content) - end - - context 'when is valid call stack profile' do - let(:content) { 'This is a call stack request profile' } - let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_execution.html" } - - it 'displays the content' do - visit admin_requests_profile_path(profile) - - expect(page).to have_content(content) - end - end - - context 'when is valid memory profile' do - let(:content) { 'This is a memory request profile' } - let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_memory.txt" } - - it 'displays the content' do - visit admin_requests_profile_path(profile) - - expect(page).to have_content(content) - end - end - end - - context 'when a profile does not exist' do - it 'shows an error message' do - visit admin_requests_profile_path('|non|existent_12345.html') - - expect(page).to have_content('Profile not found') - end - end - end -end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index eec7fbf625b..25ff4022454 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -472,7 +472,7 @@ RSpec.describe "Admin Runners" do click_on 'Reset registration token' within_modal do - click_button('OK', match: :first) + click_button('Reset token', match: :first) end wait_for_requests diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index d50ac0529d6..dda758898e3 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -154,22 +154,6 @@ describe('diffs/components/app', () => { }); }); - it.each` - props | state | expected - ${{ isFluidLayout: true }} | ${{ isParallelView: false }} | ${false} - ${{}} | ${{ isParallelView: false }} | ${true} - ${{}} | ${{ showTreeList: true, diffFiles: [{}], isParallelView: false }} | ${false} - ${{}} | ${{ showTreeList: false, diffFiles: [{}], isParallelView: false }} | ${true} - ${{}} | ${{ showTreeList: false, diffFiles: [], isParallelView: false }} | ${true} - `( - 'uses container-limiting classes ($expected) with state ($state) and props ($props)', - ({ props, state, expected }) => { - createComponent(props, ({ state: origState }) => Object.assign(origState.diffs, state)); - - expect(wrapper.find('.container-limited.limit-container-width').exists()).toBe(expected); - }, - ); - it('displays loading icon on loading', () => { createComponent({}, ({ state }) => { state.diffs.isLoading = true; @@ -498,7 +482,6 @@ describe('diffs/components/app', () => { expect(wrapper.find(CompareVersions).exists()).toBe(true); expect(wrapper.find(CompareVersions).props()).toEqual( expect.objectContaining({ - isLimitedContainer: false, diffFilesCountText: null, }), ); diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js index 9726df55c6f..8cc342e45a7 100644 --- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js +++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js @@ -2,7 +2,7 @@ import { shallowMount, mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue'; -import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '~/diffs/constants'; +import { EVT_EXPAND_ALL_FILES } from '~/diffs/constants'; import eventHub from '~/diffs/event_hub'; import createStore from '~/diffs/store/modules'; @@ -13,7 +13,6 @@ const propsData = { mergeable: true, resolutionPath: 'a-path', }; -const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' '); async function files(store, count) { const copies = Array(count).fill(file); @@ -51,20 +50,6 @@ describe('CollapsedFilesWarning', () => { }); describe('when there is more than one file', () => { - it.each` - limited | containerClasses - ${true} | ${limitedClasses} - ${false} | ${[]} - `( - 'has the correct container classes when limited is $limited', - async ({ limited, containerClasses }) => { - createComponent({ limited }); - await files(store, 2); - - expect(wrapper.classes()).toEqual(['col-12'].concat(containerClasses)); - }, - ); - it.each` present | dismissed ${false} | ${true} diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index f11b60a9238..21f3ee26bf8 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -38,7 +38,6 @@ describe('CompareVersions', () => { }, }); }; - const findLimitedContainer = () => wrapper.find('.container-limited.limit-container-width'); const findCompareSourceDropdown = () => wrapper.find('.mr-version-dropdown'); const findCompareTargetDropdown = () => wrapper.find('.mr-version-compare-dropdown'); const getCommitNavButtonsElement = () => wrapper.find('.commit-nav-buttons'); @@ -98,18 +97,6 @@ describe('CompareVersions', () => { expect(inlineBtn.html()).toContain('Inline'); expect(parallelBtn.html()).toContain('Side-by-side'); }); - - it('adds container-limiting classes when showFileTree is false with inline diffs', () => { - createWrapper({ isLimitedContainer: true }); - - expect(findLimitedContainer().exists()).toBe(true); - }); - - it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { - createWrapper({ isLimitedContainer: false }); - - expect(findLimitedContainer().exists()).toBe(false); - }); }); describe('noChangedFiles', () => { diff --git a/spec/frontend/diffs/components/merge_conflict_warning_spec.js b/spec/frontend/diffs/components/merge_conflict_warning_spec.js index 2f303f25f66..4e47249f5b4 100644 --- a/spec/frontend/diffs/components/merge_conflict_warning_spec.js +++ b/spec/frontend/diffs/components/merge_conflict_warning_spec.js @@ -1,13 +1,11 @@ import { shallowMount, mount } from '@vue/test-utils'; import MergeConflictWarning from '~/diffs/components/merge_conflict_warning.vue'; -import { CENTERED_LIMITED_CONTAINER_CLASSES } from '~/diffs/constants'; const propsData = { limited: true, mergeable: true, resolutionPath: 'a-path', }; -const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' '); function findResolveButton(wrapper) { return wrapper.find('.gl-alert-actions a.gl-button:first-child'); @@ -31,19 +29,6 @@ describe('MergeConflictWarning', () => { wrapper.destroy(); }); - it.each` - limited | containerClasses - ${true} | ${limitedClasses} - ${false} | ${[]} - `( - 'has the correct container classes when limited is $limited', - ({ limited, containerClasses }) => { - createComponent({ limited }); - - expect(wrapper.classes()).toEqual(containerClasses); - }, - ); - it.each` present | resolutionPath ${false} | ${''} diff --git a/spec/graphql/types/user_preferences_type_spec.rb b/spec/graphql/types/user_preferences_type_spec.rb new file mode 100644 index 00000000000..fac45443290 --- /dev/null +++ b/spec/graphql/types/user_preferences_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::UserPreferencesType do + specify { expect(described_class.graphql_name).to eq('UserPreferences') } + + it 'exposes the expected fields' do + expected_fields = %i[ + issues_sort + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/lib/gitlab/backtrace_cleaner_spec.rb b/spec/lib/gitlab/backtrace_cleaner_spec.rb index e46a90e8606..cdde5a02d3b 100644 --- a/spec/lib/gitlab/backtrace_cleaner_spec.rb +++ b/spec/lib/gitlab/backtrace_cleaner_spec.rb @@ -25,7 +25,6 @@ RSpec.describe Gitlab::BacktraceCleaner do "app/models/repository.rb:113:in `commit'", "lib/gitlab/i18n.rb:50:in `with_locale'", "lib/gitlab/middleware/multipart.rb:95:in `call'", - "lib/gitlab/request_profiler/middleware.rb:14:in `call'", "ee/lib/gitlab/database/load_balancing/rack_middleware.rb:37:in `call'", "ee/lib/gitlab/jira/middleware.rb:15:in `call'" ] diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 7e3de32b965..d6f0d7bffe8 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -442,6 +442,60 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + describe '#remove_foreign_key_if_exists' do + context 'when the foreign key does not exist' do + before do + allow(model).to receive(:foreign_key_exists?).and_return(false) + end + + it 'does nothing' do + expect(model).not_to receive(:remove_foreign_key) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id) + end + end + + context 'when the foreign key exists' do + before do + allow(model).to receive(:foreign_key_exists?).and_return(true) + end + + it 'removes the foreign key' do + expect(model).to receive(:remove_foreign_key).with(:projects, :users, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id) + end + + context 'when the target table is not given' do + it 'passes the options as the second parameter' do + expect(model).to receive(:remove_foreign_key).with(:projects, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, column: :user_id) + end + end + + context 'when the reverse_lock_order option is given' do + it 'requests for lock before removing the foreign key' do + expect(model).to receive(:transaction_open?).and_return(true) + expect(model).to receive(:execute).with(/LOCK TABLE users, projects/) + expect(model).not_to receive(:remove_foreign_key).with(:projects, :users) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id, reverse_lock_order: true) + end + + context 'when not inside a transaction' do + it 'does not lock' do + expect(model).to receive(:transaction_open?).and_return(false) + expect(model).not_to receive(:execute).with(/LOCK TABLE users, projects/) + expect(model).to receive(:remove_foreign_key).with(:projects, :users, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id, reverse_lock_order: true) + end + end + end + end + end + describe '#add_concurrent_foreign_key' do before do allow(model).to receive(:foreign_key_exists?).and_return(false) diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index dc35b9c3320..4867940c41e 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -13,7 +13,6 @@ RSpec.describe 'cross-database foreign keys' do %w( ci_build_report_results.project_id ci_builds.project_id - ci_builds_metadata.project_id ci_daily_build_group_report_results.group_id ci_daily_build_group_report_results.project_id ci_freeze_periods.project_id diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb deleted file mode 100644 index 30e23a99b22..00000000000 --- a/spec/lib/gitlab/request_profiler/profile_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::RequestProfiler::Profile do - let(:profile) { described_class.new(filename) } - - describe '.new' do - context 'using old filename' do - let(:filename) { '|api|v4|version.txt_1562854738.html' } - - it 'returns valid data' do - expect(profile).to be_valid - expect(profile.request_path).to eq('/api/v4/version.txt') - expect(profile.time).to eq(Time.at(1562854738).utc) - expect(profile.type).to eq('html') - end - end - - context 'using new filename' do - let(:filename) { '|api|v4|version.txt_1563547949_execution.html' } - - it 'returns valid data' do - expect(profile).to be_valid - expect(profile.request_path).to eq('/api/v4/version.txt') - expect(profile.profile_mode).to eq('execution') - expect(profile.time).to eq(Time.at(1563547949).utc) - expect(profile.type).to eq('html') - end - end - end - - describe '#content_type' do - context 'when using html file' do - let(:filename) { '|api|v4|version.txt_1562854738_memory.html' } - - it 'returns valid data' do - expect(profile).to be_valid - expect(profile.content_type).to eq('text/html') - end - end - - context 'when using text file' do - let(:filename) { '|api|v4|version.txt_1562854738_memory.txt' } - - it 'returns valid data' do - expect(profile).to be_valid - expect(profile.content_type).to eq('text/plain') - end - end - - context 'when file is unknown' do - let(:filename) { '|api|v4|version.txt_1562854738_memory.xxx' } - - it 'returns valid data' do - expect(profile).not_to be_valid - expect(profile.content_type).to be_nil - end - end - end -end diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb deleted file mode 100644 index 4d3b361efcb..00000000000 --- a/spec/lib/gitlab/request_profiler_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::RequestProfiler do - describe '.profile_token' do - it 'returns a token' do - expect(described_class.profile_token).to be_present - end - - it 'caches the token' do - expect(Rails.cache).to receive(:fetch).with('profile-token') - - described_class.profile_token - end - end - - context 'with temporary PROFILES_DIR' do - let(:tmpdir) { Dir.mktmpdir('profiler-test') } - let(:profile_name) { '|api|v4|version.txt_1562854738_memory.html' } - let(:profile_path) { File.join(tmpdir, profile_name) } - - before do - stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir) - FileUtils.touch(profile_path) - end - - after do - FileUtils.rm_rf(tmpdir) - end - - describe '.remove_all_profiles' do - it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do - described_class.remove_all_profiles - - expect(Dir.exist?(tmpdir)).to be false - end - end - - describe '.all' do - subject { described_class.all } - - it 'returns all profiles' do - expect(subject.map(&:name)).to contain_exactly(profile_name) - end - end - - describe '.find' do - subject { described_class.find(profile_name) } - - it 'returns all profiles' do - expect(subject.name).to eq(profile_name) - end - end - end -end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index b2ffb34da1d..5e30f9160cd 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -133,4 +133,11 @@ RSpec.describe Ci::BuildMetadata do expect(build.cancel_gracefully?).to be false end end + + context 'loose foreign key on ci_builds_metadata.project_id' do + it_behaves_like 'cleanup by a loose foreign key' do + let!(:parent) { create(:project) } + let!(:model) { create(:ci_build_metadata, project: parent) } + end + end end diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb new file mode 100644 index 00000000000..e1c7fd9d60d --- /dev/null +++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::UserPreferences::Update do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + + let(:sort_value) { 'TITLE_ASC' } + + let(:input) do + { + 'issuesSort' => sort_value + } + end + + let(:mutation) { graphql_mutation(:userPreferencesUpdate, input) } + let(:mutation_response) { graphql_mutation_response(:userPreferencesUpdate) } + + context 'when user has no existing preference' do + it 'creates the user preference record' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['userPreferences']['issuesSort']).to eq(sort_value) + + expect(current_user.user_preference.persisted?).to eq(true) + expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) + end + end + + context 'when user has existing preference' do + before do + current_user.create_user_preference!(issues_sort: Types::IssueSortEnum.values['TITLE_DESC'].value) + end + + it 'updates the existing value' do + post_graphql_mutation(mutation, current_user: current_user) + + current_user.user_preference.reload + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['userPreferences']['issuesSort']).to eq(sort_value) + + expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) + end + end +end diff --git a/spec/requests/recursive_webhook_detection_spec.rb b/spec/requests/recursive_webhook_detection_spec.rb index a3014bf1d73..fe27c90b6c8 100644 --- a/spec/requests/recursive_webhook_detection_spec.rb +++ b/spec/requests/recursive_webhook_detection_spec.rb @@ -11,6 +11,11 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red let_it_be(:project_hook) { create(:project_hook, project: project, merge_requests_events: true) } let_it_be(:system_hook) { create(:system_hook, merge_requests_events: true) } + let(:stubbed_project_hook_hostname) { stubbed_hostname(project_hook.url, hostname: stubbed_project_hook_ip_address) } + let(:stubbed_system_hook_hostname) { stubbed_hostname(system_hook.url, hostname: stubbed_system_hook_ip_address) } + let(:stubbed_project_hook_ip_address) { '8.8.8.8' } + let(:stubbed_system_hook_ip_address) { '8.8.8.9' } + # Trigger a change to the merge request to fire the webhooks. def trigger_web_hooks params = { merge_request: { description: FFaker::Lorem.sentence } } @@ -18,8 +23,8 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red end def stub_requests - stub_full_request(project_hook.url, method: :post, ip_address: '8.8.8.8') - stub_full_request(system_hook.url, method: :post, ip_address: '8.8.8.9') + stub_full_request(project_hook.url, method: :post, ip_address: stubbed_project_hook_ip_address) + stub_full_request(system_hook.url, method: :post, ip_address: stubbed_system_hook_ip_address) end before do @@ -37,10 +42,10 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red trigger_web_hooks - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)) + expect(WebMock).to have_requested(:post, stubbed_project_hook_hostname) .with { |req| req.headers['X-Gitlab-Event-Uuid'] == uuid } .once - expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)) + expect(WebMock).to have_requested(:post, stubbed_system_hook_hostname) .with { |req| req.headers['X-Gitlab-Event-Uuid'] == uuid } .once end @@ -54,24 +59,24 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red Gitlab::WebHooks::RecursionDetection.set_request_uuid(nil) end - it 'executes all webhooks and logs an error for the recursive hook', :aggregate_failures do + it 'blocks and logs an error for the recursive webhook, but execute the non-recursive webhook', :aggregate_failures do stub_requests expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, recursion_detection: { uuid: uuid, ids: [project_hook.id] } ) - ).twice # Twice: once in `#async_execute`, and again in `#execute`. + ).once trigger_web_hooks - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).once - expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).once + expect(WebMock).not_to have_requested(:post, stubbed_project_hook_hostname) + expect(WebMock).to have_requested(:post, stubbed_system_hook_hostname).once end end @@ -87,35 +92,35 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red Gitlab::WebHooks::RecursionDetection.set_request_uuid(nil) end - it 'executes and logs errors for all hooks', :aggregate_failures do + it 'blocks and logs errors for all hooks', :aggregate_failures do stub_requests previous_hook_ids = previous_hooks.map(&:id) expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, recursion_detection: { uuid: uuid, ids: include(*previous_hook_ids) } ) - ).twice + ).once expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: system_hook.id, recursion_detection: { uuid: uuid, ids: include(*previous_hook_ids) } ) - ).twice + ).once trigger_web_hooks - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).once - expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).once + expect(WebMock).not_to have_requested(:post, stubbed_project_hook_hostname) + expect(WebMock).not_to have_requested(:post, stubbed_system_hook_hostname) end end end @@ -156,10 +161,10 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red expect(uuid_headers).to all(be_present) expect(uuid_headers.uniq.length).to eq(2) - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)) + expect(WebMock).to have_requested(:post, stubbed_project_hook_hostname) .with { |req| uuid_headers.include?(req.headers['X-Gitlab-Event-Uuid']) } .once - expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)) + expect(WebMock).to have_requested(:post, stubbed_system_hook_hostname) .with { |req| uuid_headers.include?(req.headers['X-Gitlab-Event-Uuid']) } .once end @@ -175,8 +180,8 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red expect(uuid_headers).to all(be_present) expect(uuid_headers.length).to eq(4) expect(uuid_headers.uniq.length).to eq(4) - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).twice - expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).twice + expect(WebMock).to have_requested(:post, stubbed_project_hook_hostname).twice + expect(WebMock).to have_requested(:post, stubbed_system_hook_hostname).twice end end end diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb deleted file mode 100644 index 72689595480..00000000000 --- a/spec/requests/request_profiler_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Request Profiler' do - let(:user) { create(:user) } - - shared_examples 'profiling a request' do |profile_type, extension| - before do - allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new) - allow(RubyProf::Profile).to receive(:profile) do |&blk| - blk.call - RubyProf::Profile.new - end - allow(MemoryProfiler).to receive(:report) do |&blk| - blk.call - MemoryProfiler.start - MemoryProfiler.stop - end - end - - it 'creates a profile of the request' do - project = create(:project, namespace: user.namespace) - time = Time.now - path = "/#{project.full_path}" - - travel_to(time) do - get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token, 'X-Profile-Mode' => profile_type } - end - - profile_type = 'execution' if profile_type.nil? - profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}_#{profile_type}.#{extension}" - expect(File.exist?(profile_path)).to be true - end - - after do - Gitlab::RequestProfiler.remove_all_profiles - end - end - - context "when user is logged-in" do - before do - login_as(user) - end - - include_examples 'profiling a request', 'execution', 'html' - include_examples 'profiling a request', nil, 'html' - include_examples 'profiling a request', 'memory', 'txt' - end - - context "when user is not logged-in" do - include_examples 'profiling a request', 'execution', 'html' - include_examples 'profiling a request', nil, 'html' - include_examples 'profiling a request', 'memory', 'txt' - end -end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 7d933ea9c5c..14aa4e364a1 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -149,13 +149,13 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state .once end - it 'executes and logs if a recursive web hook is detected', :aggregate_failures do + it 'blocks and logs if a recursive web hook is detected', :aggregate_failures do stub_full_request(project_hook.url, method: :post) Gitlab::WebHooks::RecursionDetection.register!(project_hook) expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, hook_type: 'ProjectHook', hook_name: 'push_hooks', @@ -166,12 +166,10 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state service_instance.execute - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)) - .with(headers: headers) - .once + expect(WebMock).not_to have_requested(:post, stubbed_hostname(project_hook.url)) end - it 'executes and logs if the recursion count limit would be exceeded', :aggregate_failures do + it 'blocks and logs if the recursion count limit would be exceeded', :aggregate_failures do stub_full_request(project_hook.url, method: :post) stub_const("#{Gitlab::WebHooks::RecursionDetection.name}::COUNT_LIMIT", 3) previous_hooks = create_list(:project_hook, 3) @@ -179,7 +177,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, hook_type: 'ProjectHook', hook_name: 'push_hooks', @@ -190,9 +188,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state service_instance.execute - expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)) - .with(headers: headers) - .once + expect(WebMock).not_to have_requested(:post, stubbed_hostname(project_hook.url)) end it 'handles exceptions' do @@ -496,15 +492,15 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state Gitlab::WebHooks::RecursionDetection.set_request_uuid(SecureRandom.uuid) end - it 'queues a worker and logs an error if the call chain limit would be exceeded' do + it 'does not queue a worker and logs an error if the call chain limit would be exceeded' do stub_const("#{Gitlab::WebHooks::RecursionDetection.name}::COUNT_LIMIT", 3) previous_hooks = create_list(:project_hook, 3) previous_hooks.each { Gitlab::WebHooks::RecursionDetection.register!(_1) } - expect(WebHookWorker).to receive(:perform_async) + expect(WebHookWorker).not_to receive(:perform_async) expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, hook_type: 'ProjectHook', hook_name: 'push_hooks', @@ -519,13 +515,13 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state service_instance.async_execute end - it 'queues a worker and logs an error if a recursive call chain is detected' do + it 'does not queue a worker and logs an error if a recursive call chain is detected' do Gitlab::WebHooks::RecursionDetection.register!(project_hook) - expect(WebHookWorker).to receive(:perform_async) + expect(WebHookWorker).not_to receive(:perform_async) expect(Gitlab::AuthLogger).to receive(:error).with( include( - message: 'Webhook recursion detected and will be blocked in future', + message: 'Recursive webhook blocked from executing', hook_id: project_hook.id, hook_type: 'ProjectHook', hook_name: 'push_hooks',