summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--app/assets/javascripts/environments/components/enable_review_app_button.vue107
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue24
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js1
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js6
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--changelogs/unreleased/118844-one-button-review-app-fe.yml5
-rw-r--r--changelogs/unreleased/36675-add-median-line-to-cycle-analytics-duration-chart.yml5
-rw-r--r--changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml5
-rw-r--r--doc/development/code_review.md6
-rw-r--r--doc/development/migration_style_guide.md4
-rw-r--r--doc/development/reactive_caching.md276
-rw-r--r--doc/development/utilities.md275
-rw-r--r--doc/user/analytics/cycle_analytics.md17
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_mr_widget.pngbin22175 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_mr_widget_v12_7.pngbin0 -> 198351 bytes
-rw-r--r--doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_4.pngbin26902 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_7.pngbin0 -> 88749 bytes
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md6
-rw-r--r--lib/gitlab/auth/current_user_mode.rb61
-rw-r--r--lib/gitlab/diff/highlight_cache.rb6
-rw-r--r--lib/gitlab/sidekiq_middleware.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/admin_mode/client.rb34
-rw-r--r--lib/gitlab/sidekiq_middleware/admin_mode/server.rb24
-rw-r--r--locale/gitlab.pot30
-rw-r--r--qa/qa/page/project/pipeline/show.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb102
-rw-r--r--spec/features/admin/admin_mode/workers_spec.rb77
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb66
-rw-r--r--spec/frontend/environments/enable_review_app_button_spec.js31
-rw-r--r--spec/javascripts/environments/environments_app_spec.js20
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb172
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb7
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb94
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb72
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb6
-rw-r--r--spec/models/user_spec.rb6
-rw-r--r--spec/policies/base_policy_spec.rb10
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb45
39 files changed, 677 insertions, 941 deletions
diff --git a/README.md b/README.md
index b3b66959884..1372e47d52f 100644
--- a/README.md
+++ b/README.md
@@ -82,9 +82,9 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ruby (MRI) 2.6.5
- Git 2.8.4+
- Redis 2.8+
-- PostgreSQL (preferred) or MySQL
+- PostgreSQL 9.6+
-For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
+For more information please see the [architecture](https://docs.gitlab.com/ee/development/architecture.html) and [requirements](https://docs.gitlab.com/ee/install/requirements.html) documentation.
## UX design
diff --git a/app/assets/javascripts/environments/components/enable_review_app_button.vue b/app/assets/javascripts/environments/components/enable_review_app_button.vue
new file mode 100644
index 00000000000..2f9e9cb628f
--- /dev/null
+++ b/app/assets/javascripts/environments/components/enable_review_app_button.vue
@@ -0,0 +1,107 @@
+<script>
+import { GlButton, GlModal, GlModalDirective, GlLink, GlSprintf } from '@gitlab/ui';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ GlLink,
+ GlModal,
+ GlSprintf,
+ ModalCopyButton,
+ },
+ directives: {
+ 'gl-modal': GlModalDirective,
+ },
+ instructionText: {
+ step1: s__(
+ 'EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}.',
+ ),
+ step2: s__('EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:'),
+ step3: s__(
+ `EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file.`,
+ ),
+ },
+ modalInfo: {
+ closeText: s__('EnableReviewApp|Close'),
+ copyToClipboardText: s__('EnableReviewApp|Copy snippet text'),
+ copyString: `deploy_review
+ stage: deploy
+ script:
+ - echo "Deploy a review app"
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://$CI_ENVIRONMENT_SLUG.example.com
+ only: branches
+ except: master`,
+ id: 'enable-review-app-info',
+ title: s__('ReviewApp|Enable Review App'),
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-button
+ v-gl-modal="$options.modalInfo.id"
+ variant="info"
+ category="secondary"
+ type="button"
+ class="js-enable-review-app-button"
+ >
+ {{ s__('Environments|Enable review app') }}
+ </gl-button>
+ <gl-modal
+ :modal-id="$options.modalInfo.id"
+ :title="$options.modalInfo.title"
+ size="lg"
+ class="text-2 ws-normal"
+ ok-only
+ ok-variant="light"
+ :ok-title="$options.modalInfo.closeText"
+ >
+ <p>
+ <gl-sprintf :message="$options.instructionText.step1">
+ <template #step="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ <template #link="{ content }">
+ <gl-link
+ href="https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html"
+ target="_blank"
+ >{{ content }}</gl-link
+ >
+ </template>
+ </gl-sprintf>
+ </p>
+ <div>
+ <p>
+ <gl-sprintf :message="$options.instructionText.step2">
+ <template #step="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <div class="flex align-items-start">
+ <pre class="w-100"> {{ $options.modalInfo.copyString }} </pre>
+ <modal-copy-button
+ :title="$options.modalInfo.copyToClipboardText"
+ :text="$options.modalInfo.copyString"
+ :modal-id="$options.modalInfo.id"
+ css-classes="border-0"
+ />
+ </div>
+ </div>
+ <p>
+ <gl-sprintf :message="$options.instructionText.step3">
+ <template #step="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ <template #link="{ content }">
+ <gl-link href="blob/master/.gitlab-ci.yml" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 50c667e6966..07b8d20fde0 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -1,19 +1,23 @@
<script>
+import { GlButton } from '@gitlab/ui';
import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin';
-import Flash from '../../flash';
-import { s__ } from '../../locale';
+import Flash from '~/flash';
+import { s__ } from '~/locale';
import emptyState from './empty_state.vue';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
-import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
+import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin';
+import EnableReviewAppButton from './enable_review_app_button.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
export default {
components: {
+ ConfirmRollbackModal,
emptyState,
+ EnableReviewAppButton,
+ GlButton,
StopEnvironmentModal,
- ConfirmRollbackModal,
},
mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin],
@@ -96,10 +100,16 @@ export default {
<div class="top-area">
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
- <div v-if="canCreateEnvironment && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-success">
+ <div class="nav-controls">
+ <enable-review-app-button v-if="state.reviewAppDetails.can_setup_review_app" class="mr-2" />
+ <gl-button
+ v-if="canCreateEnvironment && !isLoading"
+ :href="newEnvironmentPath"
+ category="primary"
+ variant="success"
+ >
{{ s__('Environments|New environment') }}
- </a>
+ </gl-button>
</div>
</div>
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 34374e306a4..1c5884b541c 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -52,6 +52,7 @@ export default {
this.store.storeAvailableCount(resp.data.available_count);
this.store.storeStoppedCount(resp.data.stopped_count);
this.store.storeEnvironments(resp.data.environments);
+ this.store.setReviewAppDetails(resp.data.review_app);
this.store.setPagination(resp.headers);
}
},
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index 81c257acd53..6b7c1ff627d 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -14,6 +14,7 @@ export default class EnvironmentsStore {
this.state.stoppedCounter = 0;
this.state.availableCounter = 0;
this.state.paginationInformation = {};
+ this.state.reviewAppDetails = {};
return this;
}
@@ -104,6 +105,11 @@ export default class EnvironmentsStore {
return paginationInformation;
}
+ setReviewAppDetails(details = {}) {
+ this.state.reviewAppDetails = details;
+ return details;
+ }
+
/**
* Stores the number of available environments.
*
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7cb629dee21..fa88ca91170 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -37,7 +37,6 @@ class ApplicationController < ActionController::Base
around_action :set_current_context
around_action :set_locale
around_action :set_session_storage
- around_action :set_current_admin
after_action :set_page_title_header, if: :json_request?
after_action :limit_session_time, if: -> { !current_user }
@@ -474,13 +473,6 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
- def set_current_admin(&block)
- return yield unless Feature.enabled?(:user_mode_in_session)
- return yield unless current_user
-
- Gitlab::Auth::CurrentUserMode.with_current_admin(current_user, &block)
- end
-
def html_request?
request.format.html?
end
diff --git a/changelogs/unreleased/118844-one-button-review-app-fe.yml b/changelogs/unreleased/118844-one-button-review-app-fe.yml
new file mode 100644
index 00000000000..f7a3cf6dd72
--- /dev/null
+++ b/changelogs/unreleased/118844-one-button-review-app-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Create conditional Enable Review App button
+merge_request: 23703
+author:
+type: added
diff --git a/changelogs/unreleased/36675-add-median-line-to-cycle-analytics-duration-chart.yml b/changelogs/unreleased/36675-add-median-line-to-cycle-analytics-duration-chart.yml
new file mode 100644
index 00000000000..f9ca6b1a2d2
--- /dev/null
+++ b/changelogs/unreleased/36675-add-median-line-to-cycle-analytics-duration-chart.yml
@@ -0,0 +1,5 @@
+---
+title: Add cycle analytics duration chart with median line
+merge_request: 23971
+author:
+type: added
diff --git a/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml b/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml
deleted file mode 100644
index 37e34b3174c..00000000000
--- a/changelogs/unreleased/refactor-admin-mode-in-sidekiq-jobs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Admin mode support in sidekiq jobs
-merge_request: 21792
-author: Diego Louzán
-type: changed
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 9b82cf5ff99..3d41ff64380 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -347,6 +347,12 @@ reviewee.
of the contributed code. It's usually a good idea to ask another maintainer or
reviewer before doing it, but have the courage to do it when you believe it is
important.
+- In the interest of [Iteration](https://about.gitlab.com/handbook/values/#iteration),
+ if, as a reviewer, your suggestions are non-blocking changes or personal preference
+ (not a documented or agreed requirement), consider approving the merge request
+ before passing it back to the author. This allows them to implement your suggestions
+ if they agree, or allows them to pass it onto the
+ maintainer for review straight away. This can help reduce our overall time-to-merge.
- There is a difference in doing things right and doing things right now.
Ideally, we should do the former, but in the real world we need the latter as
well. A good example is a security fix which should be released as soon as
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index cccea4ee9f4..209fa5d610e 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -379,10 +379,6 @@ Rails migration example:
```ruby
add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
-
-# or
-
-add_column(:projects, :foo, :integer, default: 10, limit: 8)
```
## Timestamp column type
diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md
new file mode 100644
index 00000000000..de93a5aa1d0
--- /dev/null
+++ b/doc/development/reactive_caching.md
@@ -0,0 +1,276 @@
+# `ReactiveCaching`
+
+> This doc refers to <https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/reactive_caching.rb>.
+
+The `ReactiveCaching` concern is used for fetching some data in the background and store it
+in the Rails cache, keeping it up-to-date for as long as it is being requested. If the
+data hasn't been requested for `reactive_cache_lifetime`, it will stop being refreshed,
+and then be removed.
+
+## Examples
+
+```ruby
+class Foo < ApplicationRecord
+ include ReactiveCaching
+
+ after_save :clear_reactive_cache!
+
+ def calculate_reactive_cache(param1, param2)
+ # Expensive operation here. The return value of this method is cached
+ end
+
+ def result
+ # Any arguments can be passed to `with_reactive_cache`. `calculate_reactive_cache`
+ # will be called with the same arguments.
+ with_reactive_cache(param1, param2) do |data|
+ # ...
+ end
+ end
+end
+```
+
+In this example, the first time `#result` is called, it will return `nil`. However,
+it will enqueue a background worker to call `#calculate_reactive_cache` and set an
+initial cache lifetime of 10 min.
+
+## How it works
+
+The first time `#with_reactive_cache` is called, a background job is enqueued and
+`with_reactive_cache` returns `nil`. The background job calls `#calculate_reactive_cache`
+and stores its return value. It also re-enqueues the background job to run again after
+`reactive_cache_refresh_interval`. Therefore, it will keep the stored value up to date.
+Calculations never run concurrently.
+
+Calling `#with_reactive_cache` while a value is cached will call the block given to
+`#with_reactive_cache`, yielding the cached value. It will also extend the lifetime
+of the cache by the `reactive_cache_lifetime` value.
+
+Once the lifetime has expired, no more background jobs will be enqueued and calling
+`#with_reactive_cache` will again return `nil` - starting the process all over again.
+
+## When to use
+
+- If we need to make a request to an external API (for example, requests to the k8s API).
+It is not advisable to keep the application server worker blocked for the duration of
+the external request.
+- If a model needs to perform a lot of database calls or other time consuming
+calculations.
+
+## How to use
+
+### In models and services
+
+The ReactiveCaching concern can be used in models as well as `project_services`
+(`app/models/project_services`).
+
+1. Include the concern in your model or service.
+
+ When including in a model:
+
+ ```ruby
+ include ReactiveCaching
+ ```
+
+ or when including in a `project_service`:
+
+ ```ruby
+ include ReactiveService
+ ```
+
+1. Implement the `calculate_reactive_cache` method in your model/service.
+1. Call `with_reactive_cache` in your model/service where the cached value is needed.
+
+### In controllers
+
+Controller endpoints that call a model or service method that uses `ReactiveCaching` should
+not wait until the background worker completes.
+
+- An API that calls a model or service method that uses `ReactiveCaching` should return
+`202 accepted` when the cache is being calculated (when `#with_reactive_cache` returns `nil`).
+- It should also
+[set the polling interval header](fe_guide/performance.md#realtime-components) with
+`Gitlab::PollingInterval.set_header`.
+- The consumer of the API is expected to poll the API.
+- You can also consider implementing [ETag caching](polling.md) to reduce the server
+load caused by polling.
+
+### Methods to implement in a model or service
+
+These are methods that should be implemented in the model/service that includes `ReactiveCaching`.
+
+#### `#calculate_reactive_cache` (required)
+
+- This method must be implemented. Its return value will be cached.
+- It will be called by `ReactiveCaching` when it needs to populate the cache.
+- Any arguments passed to `with_reactive_cache` will also be passed to `calculate_reactive_cache`.
+
+#### `#reactive_cache_updated` (optional)
+
+- This method can be implemented if needed.
+- It is called by the `ReactiveCaching` concern whenever the cache is updated.
+If the cache is being refreshed and the new cache value is the same as the old cache
+value, this method will not be called. It is only called if a new value is stored in
+the cache.
+- It can be used to perform an action whenever the cache is updated.
+
+### Methods called by a model or service
+
+These are methods provided by `ReactiveCaching` and should be called in
+the model/service.
+
+#### `#with_reactive_cache` (required)
+
+- `with_reactive_cache` must be called where the result of `calculate_reactive_cache`
+is required.
+- A block can be given to `with_reactive_cache`. `with_reactive_cache` can also take
+any number of arguments. Any arguments passed to `with_reactive_cache` will be
+passed to `calculate_reactive_cache`. The arguments passed to `with_reactive_cache`
+will be appended to the cache key name.
+- If `with_reactive_cache` is called when the result has already been cached, the
+block will be called, yielding the cached value and the return value of the block
+will be returned by `with_reactive_cache`. It will also reset the timeout of the
+cache to the `reactive_cache_lifetime` value.
+- If the result has not been cached as yet, `with_reactive_cache` will return nil.
+It will also enqueue a background job, which will call `calculate_reactive_cache`
+and cache the result.
+- Once the background job has completed and the result is cached, the next call
+to `with_reactive_cache` will pick up the cached value.
+- In the example below, `data` is the cached value which is yielded to the block
+given to `with_reactive_cache`.
+
+ ```ruby
+ class Foo < ApplicationRecord
+ include ReactiveCaching
+
+ def calculate_reactive_cache(param1, param2)
+ # Expensive operation here. The return value of this method is cached
+ end
+
+ def result
+ with_reactive_cache(param1, param2) do |data|
+ # ...
+ end
+ end
+ end
+ ```
+
+#### `#clear_reactive_cache!` (optional)
+
+- This method can be called when the cache needs to be expired/cleared. For example,
+it can be called in an `after_save` callback in a model so that the cache is
+cleared after the model is modified.
+- This method should be called with the same parameters that are passed to
+`with_reactive_cache` because the parameters are part of the cache key.
+
+#### `#without_reactive_cache` (optional)
+
+- This is a convenience method that can be used for debugging purposes.
+- This method calls `calculate_reactive_cache` in the current process instead of
+in a background worker.
+
+### Configurable options
+
+There are some `class_attribute` options which can be tweaked.
+
+#### `self.reactive_cache_key`
+
+- The value of this attribute is the prefix to the `data` and `alive` cache key names.
+The parameters passed to `with_reactive_cache` form the rest of the cache key names.
+- By default, this key uses the model's name and the ID of the record.
+
+ ```ruby
+ self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
+ ```
+
+- The `data` and `alive` cache keys in this case will be `"ExampleModel:1:arg1:arg2"`
+and `"ExampleModel:1:arg1:arg2:alive"` respectively, where `ExampleModel` is the
+name of the model, `1` is the ID of the record, `arg1` and `arg2` are parameters
+passed to `with_reactive_cache`.
+- If you're including this concern in a service instead, you will need to override
+the default by adding the following to your service:
+
+ ```ruby
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
+ ```
+
+ If your reactive_cache_key is exactly like the above, you can use the existing
+ `ReactiveService` concern instead.
+
+#### `self.reactive_cache_lease_timeout`
+
+- `ReactiveCaching` uses `Gitlab::ExclusiveLease` to ensure that the cache calculation
+is never run concurrently by multiple workers.
+- This attribute is the timeout for the `Gitlab::ExclusiveLease`.
+- It defaults to 2 minutes, but can be overriden if a different timeout is required.
+
+```ruby
+self.reactive_cache_lease_timeout = 2.minutes
+```
+
+#### `self.reactive_cache_refresh_interval`
+
+- This is the interval at which the cache is refreshed.
+- It defaults to 1 minute.
+
+```ruby
+self.reactive_cache_lease_timeout = 1.minute
+```
+
+#### `self.reactive_cache_lifetime`
+
+- This is the duration after which the cache will be cleared if there are no requests.
+- The default is 10 minutes. If there are no requests for this cache value for 10 minutes,
+the cache will expire.
+- If the cache value is requested before it expires, the timeout of the cache will
+be reset to `reactive_cache_lifetime`.
+
+```ruby
+self.reactive_cache_lifetime = 10.minutes
+```
+
+#### `self.reactive_cache_worker_finder`
+
+- This is the method used by the background worker to find or generate the object on
+which `calculate_reactive_cache` can be called.
+- By default it uses the model primary key to find the object:
+
+ ```ruby
+ self.reactive_cache_worker_finder = ->(id, *_args) do
+ find_by(primary_key => id)
+ end
+ ```
+
+- The default behaviour can be overridden by defining a custom `reactive_cache_worker_finder`.
+
+ ```ruby
+ class Foo < ApplicationRecord
+ include ReactiveCaching
+
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+
+ def self.from_cache(var1, var2)
+ # This method will be called by the background worker with "bar1" and
+ # "bar2" as arguments.
+ new(var1, var2)
+ end
+
+ def initialize(var1, var2)
+ # ...
+ end
+
+ def calculate_reactive_cache(var1, var2)
+ # Expensive operation here. The return value of this method is cached
+ end
+
+ def result
+ with_reactive_cache("bar1", "bar2") do |data|
+ # ...
+ end
+ end
+ end
+ ```
+
+ - In this example, the primary key ID will be passed to `reactive_cache_worker_finder`
+ along with the parameters passed to `with_reactive_cache`.
+ - The custom `reactive_cache_worker_finder` calls `.from_cache` with the parameters
+ passed to `with_reactive_cache`.
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 561d5efc696..dfdc5c66114 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -196,277 +196,4 @@ end
## `ReactiveCaching`
-> This doc refers to <https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/reactive_caching.rb>.
-
-The `ReactiveCaching` concern is used for fetching some data in the background and store it
-in the Rails cache, keeping it up-to-date for as long as it is being requested. If the
-data hasn't been requested for `reactive_cache_lifetime`, it will stop being refreshed,
-and then be removed.
-
-### Examples
-
-```ruby
-class Foo < ApplicationRecord
- include ReactiveCaching
-
- after_save :clear_reactive_cache!
-
- def calculate_reactive_cache(param1, param2)
- # Expensive operation here. The return value of this method is cached
- end
-
- def result
- # Any arguments can be passed to `with_reactive_cache`. `calculate_reactive_cache`
- # will be called with the same arguments.
- with_reactive_cache(param1, param2) do |data|
- # ...
- end
- end
-end
-```
-
-In this example, the first time `#result` is called, it will return `nil`. However,
-it will enqueue a background worker to call `#calculate_reactive_cache` and set an
-initial cache lifetime of 10 min.
-
-### How it works
-
-The first time `#with_reactive_cache` is called, a background job is enqueued and
-`with_reactive_cache` returns `nil`. The background job calls `#calculate_reactive_cache`
-and stores its return value. It also re-enqueues the background job to run again after
-`reactive_cache_refresh_interval`. Therefore, it will keep the stored value up to date.
-Calculations never run concurrently.
-
-Calling `#with_reactive_cache` while a value is cached will call the block given to
-`#with_reactive_cache`, yielding the cached value. It will also extend the lifetime
-of the cache by the `reactive_cache_lifetime` value.
-
-Once the lifetime has expired, no more background jobs will be enqueued and calling
-`#with_reactive_cache` will again return `nil` - starting the process all over again.
-
-### When to use
-
-- If we need to make a request to an external API (for example, requests to the k8s API).
-It is not advisable to keep the application server worker blocked for the duration of
-the external request.
-- If a model needs to perform a lot of database calls or other time consuming
-calculations.
-
-### How to use
-
-#### In models and services
-
-The ReactiveCaching concern can be used in models as well as `project_services`
-(`app/models/project_services`).
-
-1. Include the concern in your model or service.
-
- When including in a model:
-
- ```ruby
- include ReactiveCaching
- ```
-
- or when including in a `project_service`:
-
- ```ruby
- include ReactiveService
- ```
-
-1. Implement the `calculate_reactive_cache` method in your model/service.
-1. Call `with_reactive_cache` in your model/service where the cached value is needed.
-
-#### In controllers
-
-Controller endpoints that call a model or service method that uses `ReactiveCaching` should
-not wait until the background worker completes.
-
-- An API that calls a model or service method that uses `ReactiveCaching` should return
-`202 accepted` when the cache is being calculated (when `#with_reactive_cache` returns `nil`).
-- It should also
-[set the polling interval header](fe_guide/performance.md#realtime-components) with
-`Gitlab::PollingInterval.set_header`.
-- The consumer of the API is expected to poll the API.
-- You can also consider implementing [ETag caching](polling.md) to reduce the server
-load caused by polling.
-
-#### Methods to implement in a model or service
-
-These are methods that should be implemented in the model/service that includes `ReactiveCaching`.
-
-##### `#calculate_reactive_cache` (required)
-
-- This method must be implemented. Its return value will be cached.
-- It will be called by `ReactiveCaching` when it needs to populate the cache.
-- Any arguments passed to `with_reactive_cache` will also be passed to `calculate_reactive_cache`.
-
-##### `#reactive_cache_updated` (optional)
-
-- This method can be implemented if needed.
-- It is called by the `ReactiveCaching` concern whenever the cache is updated.
-If the cache is being refreshed and the new cache value is the same as the old cache
-value, this method will not be called. It is only called if a new value is stored in
-the cache.
-- It can be used to perform an action whenever the cache is updated.
-
-#### Methods called by a model or service
-
-These are methods provided by `ReactiveCaching` and should be called in
-the model/service.
-
-##### `#with_reactive_cache` (required)
-
-- `with_reactive_cache` must be called where the result of `calculate_reactive_cache`
-is required.
-- A block can be given to `with_reactive_cache`. `with_reactive_cache` can also take
-any number of arguments. Any arguments passed to `with_reactive_cache` will be
-passed to `calculate_reactive_cache`. The arguments passed to `with_reactive_cache`
-will be appended to the cache key name.
-- If `with_reactive_cache` is called when the result has already been cached, the
-block will be called, yielding the cached value and the return value of the block
-will be returned by `with_reactive_cache`. It will also reset the timeout of the
-cache to the `reactive_cache_lifetime` value.
-- If the result has not been cached as yet, `with_reactive_cache` will return nil.
-It will also enqueue a background job, which will call `calculate_reactive_cache`
-and cache the result.
-- Once the background job has completed and the result is cached, the next call
-to `with_reactive_cache` will pick up the cached value.
-- In the example below, `data` is the cached value which is yielded to the block
-given to `with_reactive_cache`.
-
- ```ruby
- class Foo < ApplicationRecord
- include ReactiveCaching
-
- def calculate_reactive_cache(param1, param2)
- # Expensive operation here. The return value of this method is cached
- end
-
- def result
- with_reactive_cache(param1, param2) do |data|
- # ...
- end
- end
- end
- ```
-
-##### `#clear_reactive_cache!` (optional)
-
-- This method can be called when the cache needs to be expired/cleared. For example,
-it can be called in an `after_save` callback in a model so that the cache is
-cleared after the model is modified.
-- This method should be called with the same parameters that are passed to
-`with_reactive_cache` because the parameters are part of the cache key.
-
-##### `#without_reactive_cache` (optional)
-
-- This is a convenience method that can be used for debugging purposes.
-- This method calls `calculate_reactive_cache` in the current process instead of
-in a background worker.
-
-#### Configurable options
-
-There are some `class_attribute` options which can be tweaked.
-
-##### `self.reactive_cache_key`
-
-- The value of this attribute is the prefix to the `data` and `alive` cache key names.
-The parameters passed to `with_reactive_cache` form the rest of the cache key names.
-- By default, this key uses the model's name and the ID of the record.
-
- ```ruby
- self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
- ```
-
-- The `data` and `alive` cache keys in this case will be `"ExampleModel:1:arg1:arg2"`
-and `"ExampleModel:1:arg1:arg2:alive"` respectively, where `ExampleModel` is the
-name of the model, `1` is the ID of the record, `arg1` and `arg2` are parameters
-passed to `with_reactive_cache`.
-- If you're including this concern in a service instead, you will need to override
-the default by adding the following to your service:
-
- ```ruby
- self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
- ```
-
- If your reactive_cache_key is exactly like the above, you can use the existing
- `ReactiveService` concern instead.
-
-##### `self.reactive_cache_lease_timeout`
-
-- `ReactiveCaching` uses `Gitlab::ExclusiveLease` to ensure that the cache calculation
-is never run concurrently by multiple workers.
-- This attribute is the timeout for the `Gitlab::ExclusiveLease`.
-- It defaults to 2 minutes, but can be overriden if a different timeout is required.
-
-```ruby
-self.reactive_cache_lease_timeout = 2.minutes
-```
-
-##### `self.reactive_cache_refresh_interval`
-
-- This is the interval at which the cache is refreshed.
-- It defaults to 1 minute.
-
-```ruby
-self.reactive_cache_lease_timeout = 1.minute
-```
-
-##### `self.reactive_cache_lifetime`
-
-- This is the duration after which the cache will be cleared if there are no requests.
-- The default is 10 minutes. If there are no requests for this cache value for 10 minutes,
-the cache will expire.
-- If the cache value is requested before it expires, the timeout of the cache will
-be reset to `reactive_cache_lifetime`.
-
-```ruby
-self.reactive_cache_lifetime = 10.minutes
-```
-
-##### `self.reactive_cache_worker_finder`
-
-- This is the method used by the background worker to find or generate the object on
-which `calculate_reactive_cache` can be called.
-- By default it uses the model primary key to find the object:
-
- ```ruby
- self.reactive_cache_worker_finder = ->(id, *_args) do
- find_by(primary_key => id)
- end
- ```
-
-- The default behaviour can be overridden by defining a custom `reactive_cache_worker_finder`.
-
- ```ruby
- class Foo < ApplicationRecord
- include ReactiveCaching
-
- self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
-
- def self.from_cache(var1, var2)
- # This method will be called by the background worker with "bar1" and
- # "bar2" as arguments.
- new(var1, var2)
- end
-
- def initialize(var1, var2)
- # ...
- end
-
- def calculate_reactive_cache(var1, var2)
- # Expensive operation here. The return value of this method is cached
- end
-
- def result
- with_reactive_cache("bar1", "bar2") do |data|
- # ...
- end
- end
- end
- ```
-
- - In this example, the primary key ID will be passed to `reactive_cache_worker_finder`
- along with the parameters passed to `with_reactive_cache`.
- - The custom `reactive_cache_worker_finder` calls `.from_cache` with the parameters
- passed to `with_reactive_cache`.
+Read the documentation on [`ReactiveCaching`](reactive_caching.md).
diff --git a/doc/user/analytics/cycle_analytics.md b/doc/user/analytics/cycle_analytics.md
index e0bb4e03c41..8d3eaade759 100644
--- a/doc/user/analytics/cycle_analytics.md
+++ b/doc/user/analytics/cycle_analytics.md
@@ -172,16 +172,23 @@ For example, if 30 days worth of data has been selected (for example, 2019-12-16
median line will represent the previous 30 days worth of data (2019-11-16 to 2019-12-16)
as a metric to compare against.
-### Enabling chart
+### Disabling chart
-By default, this chart is disabled for self-managed instances. To enable it, ask an
-administrator with Rails console access to run the following:
+This chart is enabled by default. If you have a self-managed instance, an
+administrator can open a Rails console and disable it with the following command:
```ruby
-Feature.enable(:cycle_analytics_scatterplot_enabled)
+Feature.disable(:cycle_analytics_scatterplot_enabled)
```
-This chart is enabled by default on GitLab.com.
+### Disabling chart median line
+
+This chart median line is enabled by default. If you have a self-managed instance, an
+administrator can open a Rails console and disable it with the following command:
+
+```ruby
+Feature.disable(:cycle_analytics_scatterplot_median_enabled)
+```
## Permissions
diff --git a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
deleted file mode 100644
index 2598cc71c33..00000000000
--- a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_premium_mr_widget_v12_7.png b/doc/user/project/merge_requests/img/approvals_premium_mr_widget_v12_7.png
new file mode 100644
index 00000000000..f9348b0eefc
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_premium_mr_widget_v12_7.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_4.png b/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_4.png
deleted file mode 100644
index c704129685f..00000000000
--- a/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_7.png b/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_7.png
new file mode 100644
index 00000000000..c2e5714e78d
--- /dev/null
+++ b/doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_7.png
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index fa30f4e2eb4..d7bb2dd16f9 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -74,9 +74,9 @@ To enable this merge request approval rule:
1. Navigate to your project's **Settings > General** and expand
**Merge request approvals**.
-1. Locate **All members with Developer role or higher and code owners (if any)** and click **Edit** to choose the number of approvals required.
+1. Locate **Any eligible user** and choose the number of approvals required.
-![MR approvals by Code Owners](img/mr_approvals_by_code_owners_v12_4.png)
+![MR approvals by Code Owners](img/mr_approvals_by_code_owners_v12_7.png)
Once set, merge requests can only be merged once approved by the
number of approvals you've set. GitLab will accept approvals from
@@ -145,7 +145,7 @@ a rule is already defined.
When an [eligible approver](#eligible-approvers) approves a merge request, it will
reduce the number of approvals left for all rules that the approver belongs to.
-![Approvals premium merge request widget](img/approvals_premium_mr_widget.png)
+![Approvals premium merge request widget](img/approvals_premium_mr_widget_v12_7.png)
## Adding or removing an approval
diff --git a/lib/gitlab/auth/current_user_mode.rb b/lib/gitlab/auth/current_user_mode.rb
index 1ef95c03cfc..cb39baaa6cc 100644
--- a/lib/gitlab/auth/current_user_mode.rb
+++ b/lib/gitlab/auth/current_user_mode.rb
@@ -10,54 +10,12 @@ module Gitlab
class CurrentUserMode
NotRequestedError = Class.new(StandardError)
- # RequestStore entries
- CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY = { res: :current_user_mode, data: :bypass_session_admin_id }.freeze
- CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY = { res: :current_user_mode, data: :current_admin }.freeze
-
- # SessionStore entries
SESSION_STORE_KEY = :current_user_mode
- ADMIN_MODE_START_TIME_KEY = :admin_mode
- ADMIN_MODE_REQUESTED_TIME_KEY = :admin_mode_requested
+ ADMIN_MODE_START_TIME_KEY = 'admin_mode'
+ ADMIN_MODE_REQUESTED_TIME_KEY = 'admin_mode_requested'
MAX_ADMIN_MODE_TIME = 6.hours
ADMIN_MODE_REQUESTED_GRACE_PERIOD = 5.minutes
- class << self
- # Admin mode activation requires storing a flag in the user session. Using this
- # method when scheduling jobs in Sidekiq will bypass the session check for a
- # user that was already in admin mode
- def bypass_session!(admin_id)
- Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] = admin_id
-
- Gitlab::AppLogger.debug("Bypassing session in admin mode for: #{admin_id}")
-
- yield
- ensure
- Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY)
- end
-
- def bypass_session_admin_id
- Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY]
- end
-
- # Store in the current request the provided user model (only if in admin mode)
- # and yield
- def with_current_admin(admin)
- return yield unless self.new(admin).admin_mode?
-
- Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] = admin
-
- Gitlab::AppLogger.debug("Admin mode active for: #{admin.username}")
-
- yield
- ensure
- Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY)
- end
-
- def current_admin
- Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY]
- end
- end
-
def initialize(user)
@user = user
end
@@ -84,7 +42,7 @@ module Gitlab
raise NotRequestedError unless admin_mode_requested?
- reset_request_store_cache_entries
+ reset_request_store
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now
@@ -97,7 +55,7 @@ module Gitlab
def disable_admin_mode!
return unless user&.admin?
- reset_request_store_cache_entries
+ reset_request_store
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = nil
@@ -106,7 +64,7 @@ module Gitlab
def request_admin_mode!
return unless user&.admin?
- reset_request_store_cache_entries
+ reset_request_store
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = Time.now
end
@@ -115,12 +73,10 @@ module Gitlab
attr_reader :user
- # RequestStore entry to cache #admin_mode? result
def admin_mode_rs_key
@admin_mode_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode? }
end
- # RequestStore entry to cache #admin_mode_requested? result
def admin_mode_requested_rs_key
@admin_mode_requested_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode_requested? }
end
@@ -130,7 +86,6 @@ module Gitlab
end
def any_session_with_admin_mode?
- return true if bypass_session?
return true if current_session_data.initiated? && current_session_data[ADMIN_MODE_START_TIME_KEY].to_i > MAX_ADMIN_MODE_TIME.ago.to_i
all_sessions.any? do |session|
@@ -148,11 +103,7 @@ module Gitlab
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY].to_i > ADMIN_MODE_REQUESTED_GRACE_PERIOD.ago.to_i
end
- def bypass_session?
- user&.id && user.id == self.class.bypass_session_admin_id
- end
-
- def reset_request_store_cache_entries
+ def reset_request_store
Gitlab::SafeRequestStore.delete(admin_mode_rs_key)
Gitlab::SafeRequestStore.delete(admin_mode_requested_rs_key)
end
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
index 0a8fbb9a673..0cb8020bacb 100644
--- a/lib/gitlab/diff/highlight_cache.rb
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -93,6 +93,8 @@ module Gitlab
#
redis.expire(key, EXPIRATION)
end
+
+ record_metrics(redis.memory("USAGE", key))
end
# Subsequent read_file calls would need the latest cache.
@@ -101,6 +103,10 @@ module Gitlab
clear_memoization(:cacheable_files)
end
+ def record_metrics(memory_usage)
+ self.class.gitlab_redis_diff_caching_memory_usage_bytes.observe({}, memory_usage)
+ end
+
def file_paths
strong_memoize(:file_paths) do
diff_files.collect(&:file_path)
diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb
index 6c27213df49..439d45b7a14 100644
--- a/lib/gitlab/sidekiq_middleware.rb
+++ b/lib/gitlab/sidekiq_middleware.rb
@@ -17,7 +17,6 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Labkit::Middleware::Sidekiq::Server
chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger
- chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server
end
@@ -32,7 +31,6 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::ClientMetrics
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Client # needs to be before the Labkit middleware
chain.add Labkit::Middleware::Sidekiq::Client
- chain.add Gitlab::SidekiqMiddleware::AdminMode::Client
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/admin_mode/client.rb b/lib/gitlab/sidekiq_middleware/admin_mode/client.rb
deleted file mode 100644
index e227ee654ee..00000000000
--- a/lib/gitlab/sidekiq_middleware/admin_mode/client.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module SidekiqMiddleware
- module AdminMode
- # Checks if admin mode is enabled for the request creating the sidekiq job
- # by examining if admin mode has been enabled for the user
- # If enabled then it injects a job field that persists through the job execution
- class Client
- def call(_worker_class, job, _queue, _redis_pool)
- return yield unless Feature.enabled?(:user_mode_in_session)
-
- # Admin mode enabled in the original request or in a nested sidekiq job
- admin_mode_user_id = find_admin_user_id
-
- if admin_mode_user_id
- job['admin_mode_user_id'] ||= admin_mode_user_id
-
- Gitlab::AppLogger.debug("AdminMode::Client injected admin mode for job: #{job.inspect}")
- end
-
- yield
- end
-
- private
-
- def find_admin_user_id
- Gitlab::Auth::CurrentUserMode.current_admin&.id ||
- Gitlab::Auth::CurrentUserMode.bypass_session_admin_id
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/sidekiq_middleware/admin_mode/server.rb b/lib/gitlab/sidekiq_middleware/admin_mode/server.rb
deleted file mode 100644
index 6366867a0fa..00000000000
--- a/lib/gitlab/sidekiq_middleware/admin_mode/server.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module SidekiqMiddleware
- module AdminMode
- class Server
- def call(_worker, job, _queue)
- return yield unless Feature.enabled?(:user_mode_in_session)
-
- admin_mode_user_id = job['admin_mode_user_id']
-
- # Do not bypass session if this job was not enabled with admin mode on
- return yield unless admin_mode_user_id
-
- Gitlab::Auth::CurrentUserMode.bypass_session!(admin_mode_user_id) do
- Gitlab::AppLogger.debug("AdminMode::Server bypasses session for admin mode in job: #{job.inspect}")
-
- yield
- end
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 67362b2efcc..7bf7cc9051f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7137,6 +7137,21 @@ msgstr ""
msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
msgstr ""
+msgid "EnableReviewApp|%{stepStart}Step 1%{stepEnd}. Ensure you have Kubernetes set up and have a base domain for your %{linkStart}cluster%{linkEnd}."
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 2%{stepEnd}. Copy the following snippet:"
+msgstr ""
+
+msgid "EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file."
+msgstr ""
+
+msgid "EnableReviewApp|Close"
+msgstr ""
+
+msgid "EnableReviewApp|Copy snippet text"
+msgstr ""
+
msgid "Enabled"
msgstr ""
@@ -7311,6 +7326,9 @@ msgstr ""
msgid "Environments|Deployment"
msgstr ""
+msgid "Environments|Enable review app"
+msgstr ""
+
msgid "Environments|Environment"
msgstr ""
@@ -11529,6 +11547,9 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
+msgid "Manage your license"
+msgstr ""
+
msgid "Manifest"
msgstr ""
@@ -16189,6 +16210,9 @@ msgstr ""
msgid "Review time is defined as the time it takes from first comment until merged."
msgstr ""
+msgid "ReviewApp|Enable Review App"
+msgstr ""
+
msgid "Reviewing"
msgstr ""
@@ -19205,6 +19229,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
+msgstr ""
+
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
@@ -21868,9 +21895,6 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to use Geo replication."
-msgstr ""
-
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
msgstr ""
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 45fffbf6000..1003b828a32 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -30,9 +30,9 @@ module QA::Page
element :pipeline_badges
end
- def running?
+ def running?(wait: 0)
within('.ci-header-container') do
- page.has_content?('running')
+ page.has_content?('running', wait: wait)
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index d4853a7bcf3..3e000c6381e 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,78 +1,74 @@
# frozen_string_literal: true
module QA
- context 'Verify', :orchestrated, :docker do
+ context 'Verify', :docker do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
- after do
- Service::DockerRun::GitlabRunner.new(executor).remove!
- end
-
- it 'users creates a pipeline which gets processed' do
- Flow::Login.sign_in
-
- project = Resource::Project.fabricate! do |project|
- project.name = 'project-with-pipelines'
- project.description = 'Project with CI/CD Pipelines.'
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project-with-pipeline'
end
+ end
+ before do
Resource::Runner.fabricate! do |runner|
runner.project = project
runner.name = executor
- runner.tags = %w[qa test]
+ runner.tags = [executor]
end
+ end
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
- push.file_name = '.gitlab-ci.yml'
- push.commit_message = 'Add .gitlab-ci.yml'
- push.file_content = <<~EOF
- test-success:
- tags:
- - qa
- - test
- script: echo 'OK'
-
- test-failure:
- tags:
- - qa
- - test
- script:
- - echo 'FAILURE'
- - exit 1
-
- test-tags:
- tags:
- - qa
- - docker
- script: echo 'NOOP'
+ after do
+ Service::DockerRun::GitlabRunner.new(executor).remove!
+ end
- test-artifacts:
- tags:
- - qa
- - test
- script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt
- artifacts:
- paths:
- - my-artifacts/
- EOF
- end.project.visit!
+ it 'users creates a pipeline which gets processed', :smoke do
+ Flow::Login.sign_in
- expect(page).to have_content('Add .gitlab-ci.yml')
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ test-success:
+ tags:
+ - #{executor}
+ script: echo 'OK'
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ test-failure:
+ tags:
+ - #{executor}
+ script:
+ - echo 'FAILURE'
+ - exit 1
- expect(page).to have_content('All 1')
- expect(page).to have_content('Add .gitlab-ci.yml')
+ test-tags:
+ tags:
+ - invalid
+ script: echo 'NOOP'
- puts 'Waiting for the runner to process the pipeline'
- sleep 15 # Runner should process all jobs within 15 seconds.
+ test-artifacts:
+ tags:
+ - #{executor}
+ script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt
+ artifacts:
+ paths:
+ - my-artifacts/
+ YAML
+ }
+ ]
+ )
+ end.project.visit!
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
- expect(pipeline).to be_running
+ expect(pipeline).to be_running(wait: 30)
expect(pipeline).to have_build('test-success', status: :success)
expect(pipeline).to have_build('test-failure', status: :failed)
expect(pipeline).to have_build('test-tags', status: :pending)
diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb
deleted file mode 100644
index e33c9d7e64c..00000000000
--- a/spec/features/admin/admin_mode/workers_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# Test an operation that triggers background jobs requiring administrative rights
-describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do
- let(:user) { create(:user) }
- let(:user_to_delete) { create(:user) }
-
- before do
- add_sidekiq_middleware
-
- sign_in(user)
- end
-
- context 'as a regular user' do
- it 'cannot delete user' do
- visit admin_user_path(user_to_delete)
-
- expect(page).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'as an admin user' do
- let(:user) { create(:admin) }
-
- context 'when admin mode disabled' do
- it 'cannot delete user', :js do
- visit admin_user_path(user_to_delete)
-
- expect(page).to have_content('Re-authentication required')
- end
- end
-
- context 'when admin mode enabled', :delete do
- before do
- gitlab_enable_admin_mode_sign_in(user)
- end
-
- it 'can delete user', :js do
- visit admin_user_path(user_to_delete)
- click_button 'Delete user'
-
- page.within '.modal-dialog' do
- find("input[name='username']").send_keys(user_to_delete.name)
- click_button 'Delete user'
-
- wait_for_requests
- end
-
- expect(page).to have_content('The user is being deleted.')
-
- # Perform jobs while logged out so that admin mode is only enabled in job metadata
- execute_jobs_signed_out(user)
-
- visit admin_user_path(user_to_delete)
-
- expect(page).to have_title('Not Found')
- end
- end
- end
-
- def add_sidekiq_middleware
- Sidekiq::Testing.server_middleware do |chain|
- chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
- end
- end
-
- def execute_jobs_signed_out(user)
- gitlab_sign_out
-
- Sidekiq::Worker.drain_all
-
- sign_in(user)
- gitlab_enable_admin_mode_sign_in(user)
- end
-end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 954773e766d..3e8197588ed 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -2,64 +2,46 @@
require 'spec_helper'
-describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
+describe 'Admin uses repository checks' do
include StubENV
- let(:admin) { create(:admin) }
-
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(admin)
+ sign_in(create(:admin))
end
- context 'when admin mode is disabled' do
- it 'admin project page requires admin mode' do
- project = create(:project)
- visit_admin_project_page(project)
+ it 'to trigger a single check' do
+ project = create(:project)
+ visit_admin_project_page(project)
- expect(page).not_to have_css('.repository-check')
- expect(page).to have_content('Enter Admin Mode')
+ page.within('.repository-check') do
+ click_button 'Trigger repository check'
end
- end
-
- context 'when admin mode is enabled' do
- before do
- gitlab_enable_admin_mode_sign_in(admin)
- end
-
- it 'to trigger a single check', :js do
- project = create(:project)
- visit_admin_project_page(project)
- page.within('.repository-check') do
- click_button 'Trigger repository check'
- end
-
- expect(page).to have_content('Repository check was triggered')
- end
+ expect(page).to have_content('Repository check was triggered')
+ end
- it 'to see a single failed repository check', :js do
- project = create(:project)
- project.update_columns(
- last_repository_check_failed: true,
- last_repository_check_at: Time.now
- )
- visit_admin_project_page(project)
+ it 'to see a single failed repository check', :js do
+ project = create(:project)
+ project.update_columns(
+ last_repository_check_failed: true,
+ last_repository_check_at: Time.now
+ )
+ visit_admin_project_page(project)
- page.within('.alert') do
- expect(page.text).to match(/Last repository check \(just now\) failed/)
- end
+ page.within('.alert') do
+ expect(page.text).to match(/Last repository check \(just now\) failed/)
end
+ end
- it 'to clear all repository checks', :js do
- visit repository_admin_application_settings_path
+ it 'to clear all repository checks', :js do
+ visit repository_admin_application_settings_path
- expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
+ expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
- accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
+ accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
- expect(page).to have_content('Started asynchronous removal of all repository check states.')
- end
+ expect(page).to have_content('Started asynchronous removal of all repository check states.')
end
def visit_admin_project_page(project)
diff --git a/spec/frontend/environments/enable_review_app_button_spec.js b/spec/frontend/environments/enable_review_app_button_spec.js
new file mode 100644
index 00000000000..5549a1737fc
--- /dev/null
+++ b/spec/frontend/environments/enable_review_app_button_spec.js
@@ -0,0 +1,31 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import EnableReviewAppButton from '~/environments/components/enable_review_app_button.vue';
+
+describe('Enable Review App Button', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('renders button with text', () => {
+ beforeEach(() => {
+ wrapper = mount(EnableReviewAppButton);
+ });
+
+ it('renders Enable Review text', () => {
+ expect(wrapper.text()).toBe('Enable review app');
+ });
+ });
+
+ describe('renders the modal', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(EnableReviewAppButton);
+ });
+
+ it('renders the copyToClipboard button', () => {
+ expect(wrapper.find(ModalCopyButton).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 75526c2ba74..6c05b609923 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -55,6 +55,26 @@ describe('Environment', () => {
"You don't have any environments right now",
);
});
+
+ describe('when it is possible to enable a review app', () => {
+ beforeEach(done => {
+ mock
+ .onGet(mockData.endpoint)
+ .reply(200, { environments: [], review_app: { can_setup_review_app: true } });
+
+ component = mountComponent(EnvironmentsComponent, mockData);
+
+ setTimeout(() => {
+ done();
+ }, 0);
+ });
+
+ it('should render the enable review app button', () => {
+ expect(component.$el.querySelector('.js-enable-review-app-button').textContent).toContain(
+ 'Enable review app',
+ );
+ });
+ });
});
describe('with paginated environments', () => {
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
index 7c2fdac6c25..3b3db0f7315 100644
--- a/spec/lib/gitlab/auth/current_user_mode_spec.rb
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -2,10 +2,10 @@
require 'spec_helper'
-describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
+describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
include_context 'custom session'
- let(:user) { build_stubbed(:user) }
+ let(:user) { build(:user) }
subject { described_class.new(user) }
@@ -13,66 +13,54 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store
allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
end
- shared_examples 'admin mode cannot be enabled' do
- it 'is false by default' do
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'cannot be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password)
-
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'cannot be enabled with an invalid password' do
- subject.enable_admin_mode!(password: nil)
-
- expect(subject.admin_mode?).to be(false)
- end
+ describe '#admin_mode?', :request_store do
+ context 'when the user is a regular user' do
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
- it 'cannot be enabled with empty params' do
- subject.enable_admin_mode!
+ it 'cannot be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password)
- expect(subject.admin_mode?).to be(false)
- end
+ expect(subject.admin_mode?).to be(false)
+ end
- it 'disable has no effect' do
- subject.enable_admin_mode!
- subject.disable_admin_mode!
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(password: nil)
- expect(subject.admin_mode?).to be(false)
- end
+ expect(subject.admin_mode?).to be(false)
+ end
- context 'skipping password validation' do
- it 'cannot be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+ it 'cannot be enabled with empty params' do
+ subject.enable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
- it 'cannot be enabled with an invalid password' do
- subject.enable_admin_mode!(skip_password_validation: true)
+ it 'disable has no effect' do
+ subject.enable_admin_mode!
+ subject.disable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
- end
- end
- describe '#admin_mode?' do
- context 'when the user is a regular user' do
- it_behaves_like 'admin mode cannot be enabled'
+ context 'skipping password validation' do
+ it 'cannot be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
- context 'bypassing session' do
- it_behaves_like 'admin mode cannot be enabled' do
- around do |example|
- described_class.bypass_session!(user.id) { example.run }
- end
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(false)
end
end
end
context 'when the user is an admin' do
- let(:user) { build_stubbed(:user, :admin) }
+ let(:user) { build(:user, :admin) }
context 'when admin mode not requested' do
it 'is false by default' do
@@ -160,36 +148,11 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store
end
end
end
-
- context 'bypassing session' do
- it 'is active by default' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(true)
- end
- end
-
- it 'enable has no effect' do
- described_class.bypass_session!(user.id) do
- subject.request_admin_mode!
- subject.enable_admin_mode!(password: user.password)
-
- expect(subject.admin_mode?).to be(true)
- end
- end
-
- it 'disable has no effect' do
- described_class.bypass_session!(user.id) do
- subject.disable_admin_mode!
-
- expect(subject.admin_mode?).to be(true)
- end
- end
- end
end
end
describe '#enable_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ let(:user) { build(:user, :admin) }
it 'creates a timestamp in the session' do
subject.request_admin_mode!
@@ -200,7 +163,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store
end
describe '#enable_sessionless_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ let(:user) { build(:user, :admin) }
it 'enabled admin mode without password' do
subject.enable_sessionless_admin_mode!
@@ -210,7 +173,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store
end
describe '#disable_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ let(:user) { build(:user, :admin) }
it 'sets the session timestamp to nil' do
subject.request_admin_mode!
@@ -220,73 +183,6 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store
end
end
- describe '.bypass_session!' do
- context 'with a regular user' do
- it 'admin mode is false' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(false)
- expect(described_class.bypass_session_admin_id).to be(user.id)
- end
-
- expect(described_class.bypass_session_admin_id).to be_nil
- end
- end
-
- context 'with an admin user' do
- let(:user) { build_stubbed(:user, :admin) }
-
- it 'admin mode is true' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(true)
- expect(described_class.bypass_session_admin_id).to be(user.id)
- end
-
- expect(described_class.bypass_session_admin_id).to be_nil
- end
- end
- end
-
- describe '.with_current_request_admin_mode' do
- context 'with a regular user' do
- it 'user is not available inside nor outside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be_nil
- end
-
- expect(described_class.bypass_session_admin_id).to be_nil
- end
- end
-
- context 'with an admin user' do
- let(:user) { build_stubbed(:user, :admin) }
-
- context 'admin mode is disabled' do
- it 'user is not available inside nor outside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be_nil
- end
-
- expect(described_class.bypass_session_admin_id).to be_nil
- end
- end
-
- context 'admin mode is enabled' do
- before do
- subject.request_admin_mode!
- subject.enable_admin_mode!(password: user.password)
- end
-
- it 'user is available only inside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be(user)
- end
-
- expect(described_class.current_admin).to be_nil
- end
- end
- end
- end
-
def expected_session_entry(value_matcher)
{
Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 218c393c409..c1ec97ae88c 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -101,6 +101,13 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
let(:paths) { merge_request.diffs.raw_diff_files.select(&:text?).map(&:file_path) }
end
+ it 'updates memory usage metrics' do
+ expect(described_class.gitlab_redis_diff_caching_memory_usage_bytes)
+ .to receive(:observe).and_call_original
+
+ cache.send(:write_to_redis_hash, diff_hash)
+ end
+
context 'different diff_collections for the same diffable' do
before do
cache.write_if_empty
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
deleted file mode 100644
index f6449bae8c3..00000000000
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
- include AdminModeHelper
-
- let(:worker) do
- Class.new do
- def perform; end
- end
- end
-
- let(:job) { {} }
- let(:queue) { :test }
-
- it 'yields block' do
- expect do |b|
- subject.call(worker, job, queue, nil, &b)
- end.to yield_control.once
- end
-
- context 'user is a regular user' do
- it 'no admin mode field in payload' do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).not_to include('admin_mode_user_id')
- end
- end
-
- context 'user is an administrator' do
- let(:admin) { create(:admin) }
-
- context 'admin mode disabled' do
- it 'no admin mode field in payload' do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).not_to include('admin_mode_user_id')
- end
- end
-
- context 'admin mode enabled' do
- before do
- enable_admin_mode!(admin)
- end
-
- context 'when sidekiq required context not set' do
- it 'no admin mode field in payload' do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).not_to include('admin_mode_user_id')
- end
- end
-
- context 'when user stored in current request' do
- it 'has admin mode field in payload' do
- Gitlab::Auth::CurrentUserMode.with_current_admin(admin) do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).to include('admin_mode_user_id' => admin.id)
- end
- end
- end
-
- context 'when bypassing session' do
- it 'has admin mode field in payload' do
- Gitlab::Auth::CurrentUserMode.bypass_session!(admin.id) do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).to include('admin_mode_user_id' => admin.id)
- end
- end
- end
- end
- end
-
- context 'admin mode feature disabled' do
- before do
- stub_feature_flags(user_mode_in_session: false)
- end
-
- it 'yields block' do
- expect do |b|
- subject.call(worker, job, queue, nil, &b)
- end.to yield_control.once
- end
-
- it 'no admin mode field in payload' do
- subject.call(worker, job, queue, nil) { nil }
-
- expect(job).not_to include('admin_mode_user_id')
- end
- end
-end
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
deleted file mode 100644
index 60475f0e403..00000000000
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
- include AdminModeHelper
-
- let(:worker) do
- Class.new do
- def perform; end
- end
- end
-
- let(:job) { {} }
- let(:queue) { :test }
-
- it 'yields block' do
- expect do |b|
- subject.call(worker, job, queue, &b)
- end.to yield_control.once
- end
-
- context 'job has no admin mode field' do
- it 'session is not bypassed' do
- subject.call(worker, job, queue) do
- expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
- end
- end
- end
-
- context 'job has admin mode field' do
- let(:admin) { create(:admin) }
-
- context 'nil admin mode id' do
- let(:job) { { 'admin_mode_user_id' => nil } }
-
- it 'session is not bypassed' do
- subject.call(worker, job, queue) do
- expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
- end
- end
- end
-
- context 'valid admin mode id' do
- let(:job) { { 'admin_mode_user_id' => admin.id } }
-
- it 'session is bypassed' do
- subject.call(worker, job, queue) do
- expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be(admin.id)
- end
- end
- end
- end
-
- context 'admin mode feature disabled' do
- before do
- stub_feature_flags(user_mode_in_session: false)
- end
-
- it 'yields block' do
- expect do |b|
- subject.call(worker, job, queue, &b)
- end.to yield_control.once
- end
-
- it 'session is not bypassed' do
- subject.call(worker, job, queue) do
- expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 19242d25e27..e8dcbbd2ee1 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -45,8 +45,7 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::ArgumentsLogger,
Gitlab::SidekiqMiddleware::MemoryKiller,
Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
- Gitlab::SidekiqMiddleware::WorkerContext::Server,
- Gitlab::SidekiqMiddleware::AdminMode::Server
+ Gitlab::SidekiqMiddleware::WorkerContext::Server
]
end
let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares }
@@ -116,8 +115,7 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqStatus::ClientMiddleware,
Gitlab::SidekiqMiddleware::ClientMetrics,
Gitlab::SidekiqMiddleware::WorkerContext::Client,
- Labkit::Middleware::Sidekiq::Client,
- Gitlab::SidekiqMiddleware::AdminMode::Client
+ Labkit::Middleware::Sidekiq::Client
]
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 610b3e47c11..d14de7e99de 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2985,9 +2985,9 @@ describe User, :do_not_mock_admin_mode do
end
end
- describe '#can_read_all_resources?', :request_store do
+ describe '#can_read_all_resources?' do
it 'returns false for regular user' do
- user = build_stubbed(:user)
+ user = build(:user)
expect(user.can_read_all_resources?).to be_falsy
end
@@ -2995,7 +2995,7 @@ describe User, :do_not_mock_admin_mode do
context 'for admin user' do
include_context 'custom session'
- let(:user) { build_stubbed(:user, :admin) }
+ let(:user) { build(:user, :admin) }
context 'when admin mode is disabled' do
it 'returns false' do
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index ae5af9e0f29..81aee4cfcac 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -23,8 +23,8 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'read cross project' do
- let(:current_user) { build_stubbed(:user) }
- let(:user) { build_stubbed(:user) }
+ let(:current_user) { create(:user) }
+ let(:user) { create(:user) }
subject { described_class.new(current_user, [user]) }
@@ -38,7 +38,7 @@ describe BasePolicy, :do_not_mock_admin_mode do
it { is_expected.not_to be_allowed(:read_cross_project) }
context 'for admins' do
- let(:current_user) { build_stubbed(:admin) }
+ let(:current_user) { build(:admin) }
subject { described_class.new(current_user, nil) }
@@ -56,14 +56,14 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'full private access' do
- let(:current_user) { build_stubbed(:user) }
+ let(:current_user) { create(:user) }
subject { described_class.new(current_user, nil) }
it { is_expected.not_to be_allowed(:read_all_resources) }
context 'for admins' do
- let(:current_user) { build_stubbed(:admin) }
+ let(:current_user) { build(:admin) }
it 'allowed when in admin mode' do
enable_admin_mode!(current_user)
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 257b53b2a7b..81433d124c9 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -8,44 +8,40 @@ RSpec.shared_examples 'thread comments' do |resource_name|
let(:submit_selector) { "#{form_selector} .js-comment-submit-button" }
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
let(:comments_selector) { '.timeline > .note.timeline-entry' }
+ let(:comment) { 'My comment' }
- it 'clicking "Comment" will post a comment', :quarantine do
+ it 'clicking "Comment" will post a comment' do
expect(page).to have_selector toggle_selector
- find("#{form_selector} .note-textarea").send_keys('a')
+ find("#{form_selector} .note-textarea").send_keys(comment)
- find(submit_selector).click
+ click_button 'Comment'
- wait_for_requests
+ expect(page).to have_content(comment)
- find(comments_selector, match: :first)
new_comment = all(comments_selector).last
- expect(new_comment).to have_content 'a'
expect(new_comment).not_to have_selector '.discussion'
end
if resource_name == 'issue'
it "clicking 'Comment & close #{resource_name}' will post a comment and close the #{resource_name}" do
- find("#{form_selector} .note-textarea").send_keys('a')
+ find("#{form_selector} .note-textarea").send_keys(comment)
- find(close_selector).click
- wait_for_requests
+ click_button 'Comment & close issue'
- find(comments_selector, match: :first)
- find("#{comments_selector}.system-note")
- entries = all(comments_selector)
- close_note = entries.last
- new_comment = entries[-2]
+ expect(page).to have_content(comment)
+ expect(page).to have_content "@#{user.username} closed"
+
+ new_comment = all(comments_selector).last
- expect(close_note).to have_content 'closed'
expect(new_comment).not_to have_selector '.discussion'
end
end
describe 'when the toggle is clicked' do
before do
- find("#{form_selector} .note-textarea").send_keys('a')
+ find("#{form_selector} .note-textarea").send_keys(comment)
find(toggle_selector).click
end
@@ -153,10 +149,11 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
it 'clicking "Start thread" will post a thread' do
+ expect(page).to have_content(comment)
+
new_comment = all(comments_selector).last
- expect(new_comment).to have_content 'a'
- expect(new_comment).to have_selector '.discussion'
+ expect(new_comment).to have_selector('.discussion')
end
if resource_name =~ /(issue|merge request)/
@@ -208,15 +205,13 @@ RSpec.shared_examples 'thread comments' do |resource_name|
if resource_name == 'issue'
it "clicking 'Start thread & close #{resource_name}' will post a thread and close the #{resource_name}" do
- find(close_selector).click
+ click_button 'Start thread & close issue'
- find(comments_selector, match: :first)
- find("#{comments_selector}.system-note")
- entries = all(comments_selector)
- close_note = entries.last
- new_discussion = entries[-2]
+ expect(page).to have_content(comment)
+ expect(page).to have_content "@#{user.username} closed"
+
+ new_discussion = all(comments_selector)[-2]
- expect(close_note).to have_content 'closed'
expect(new_discussion).to have_selector '.discussion'
end
end