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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-10 03:10:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-10 03:10:09 +0300
commit26774b8d98581b2f211cd5cf04dcec0e352c04a6 (patch)
treefe4277340df965a25b73012973016fd26006457f
parentf820d18e56f2bd63dd0f91b076ace59345a036a1 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/admin/users/components/users_table.vue1
-rw-r--r--app/finders/deployments_finder.rb11
-rw-r--r--app/models/key.rb2
-rw-r--r--app/views/admin/users/_user.html.haml72
-rw-r--r--app/views/admin/users/_users.html.haml17
-rw-r--r--app/workers/ssh_keys/expired_notification_worker.rb27
-rw-r--r--config/feature_flags/development/deployments_finder_implicitly_enforce_ordering_for_updated_at_filter.yml8
-rw-r--r--config/feature_flags/development/vue_admin_users.yml8
-rw-r--r--db/migrate/20210609090856_add_expiry_id_ssh_key_notification_index.rb20
-rw-r--r--db/schema_migrations/202106090908561
-rw-r--r--db/structure.sql2
-rw-r--r--doc/development/documentation/styleguide/index.md9
-rw-r--r--doc/development/documentation/styleguide/word_list.md35
-rw-r--r--doc/user/application_security/container_scanning/index.md50
-rw-r--r--lib/banzai/filter/references/label_reference_filter.rb93
-rw-r--r--lib/banzai/filter/references/reference_cache.rb48
-rw-r--r--lib/banzai/filter/references/reference_filter.rb4
-rwxr-xr-xscripts/verify-tff-mapping4
-rw-r--r--spec/features/admin/users/user_spec.rb30
-rw-r--r--spec/features/admin/users/users_spec.rb417
-rw-r--r--spec/finders/deployments_finder_spec.rb24
-rw-r--r--spec/lib/banzai/filter/references/label_reference_filter_spec.rb68
-rw-r--r--spec/lib/banzai/filter/references/reference_cache_spec.rb7
-rw-r--r--spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb14
-rw-r--r--spec/views/admin/users/_user.html.haml_spec.rb51
-rw-r--r--spec/workers/ssh_keys/expired_notification_worker_spec.rb14
-rw-r--r--vendor/project_templates/cluster_management.tar.gzbin10293 -> 12723 bytes
28 files changed, 518 insertions, 523 deletions
diff --git a/app/assets/javascripts/admin/users/components/users_table.vue b/app/assets/javascripts/admin/users/components/users_table.vue
index ede5c26e487..c848908e024 100644
--- a/app/assets/javascripts/admin/users/components/users_table.vue
+++ b/app/assets/javascripts/admin/users/components/users_table.vue
@@ -107,6 +107,7 @@ export default {
:items="users"
:fields="$options.fields"
:empty-text="s__('AdminUsers|No users found')"
+ data-qa-selector="user_row_content"
show-empty
stacked="md"
:tbody-tr-attr="{ 'data-qa-selector': 'user_row_content' }"
diff --git a/app/finders/deployments_finder.rb b/app/finders/deployments_finder.rb
index acce038dba6..bb9d204ab73 100644
--- a/app/finders/deployments_finder.rb
+++ b/app/finders/deployments_finder.rb
@@ -136,7 +136,7 @@ class DeploymentsFinder
# Implicitly enforce the ordering when filtered by `updated_at` column for performance optimization.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/325627#note_552417509.
# We remove this in https://gitlab.com/gitlab-org/gitlab/-/issues/328500.
- if filter_by_updated_at? && implicitly_enforce_ordering_for_updated_at_filter?
+ if filter_by_updated_at?
sort_params.replace('updated_at' => sort_direction)
end
@@ -170,15 +170,6 @@ class DeploymentsFinder
params[:order_by].to_s == 'finished_at'
end
- def implicitly_enforce_ordering_for_updated_at_filter?
- return false unless params[:project].present?
-
- ::Feature.enabled?(
- :deployments_finder_implicitly_enforce_ordering_for_updated_at_filter,
- params[:project],
- default_enabled: :yaml)
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def preload_associations(scope)
scope.includes(
diff --git a/app/models/key.rb b/app/models/key.rb
index b8b9bd53a63..64385953865 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -7,7 +7,7 @@ class Key < ApplicationRecord
include Sortable
include Sha256Attribute
include Expirable
- include EachBatch
+ include FromUnion
sha256_attribute :fingerprint_sha256
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
deleted file mode 100644
index 2816a1061b9..00000000000
--- a/app/views/admin/users/_user.html.haml
+++ /dev/null
@@ -1,72 +0,0 @@
-.gl-responsive-table-row{ role: 'row', data: { qa_selector: 'user_row_content' } }
- .table-section.section-40
- .table-mobile-header{ role: 'rowheader' }
- = _('Name')
- .table-mobile-content
- = render 'user_detail', user: user
- .table-section.section-10
- .table-mobile-header{ role: 'rowheader' }
- = _('Projects')
- .table-mobile-content.gl-str-truncated{ data: { testid: "user-project-count-#{user.id}" } }
- = user.authorized_projects.length
- .table-section.section-15
- .table-mobile-header{ role: 'rowheader' }
- = _('Created on')
- .table-mobile-content
- = l(user.created_at.to_date, format: :admin)
- .table-section.section-15
- .table-mobile-header{ role: 'rowheader' }
- = _('Last activity')
- .table-mobile-content
- = user.last_activity_on.nil? ? _('Never') : l(user.last_activity_on, format: :admin)
- - unless user.internal?
- .table-section.section-20.table-button-footer
- .table-action-buttons{ data: { testid: "user-actions-#{user.id}" } }
- = link_to _('Edit'), edit_admin_user_path(user), class: 'btn gl-button btn-default'
- - unless user == current_user
- %button.dropdown-new.btn.gl-button.btn-default{ type: 'button', data: { testid: "dropdown-toggle", toggle: 'dropdown' } }
- = sprite_icon('settings')
- = sprite_icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-right
- %li.dropdown-header
- = _('Settings')
- %li
- - if user.ldap_blocked?
- %span.small
- = s_('AdminUsers|Cannot unblock LDAP blocked users')
- - elsif user.blocked?
- - if user.blocked_pending_approval?
- = link_to s_('AdminUsers|Approve'), approve_admin_user_path(user), method: :put
- = link_to s_('AdminUsers|Reject'), reject_admin_user_path(user), method: :delete
- - else
- %button.gl-button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_unblock_data(user) }
- = s_('AdminUsers|Unblock')
- - else
- %button.gl-button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_block_data(user, user_block_effects) }
- = s_('AdminUsers|Block')
- - if user.can_be_deactivated?
- %li
- %button.gl-button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_deactivation_data(user, user_deactivation_effects) }
- = s_('AdminUsers|Deactivate')
- - elsif user.deactivated?
- %li
- %button.gl-button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_activation_data(user) }
- = s_('AdminUsers|Activate')
- - if user.access_locked?
- %li
- = link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
- - if can?(current_user, :destroy_user, user) && !user.blocked_pending_approval?
- %li.divider
- - if user.can_be_removed?
- %li
- %button.js-delete-user-modal-button.gl-button.btn.btn-danger-tertiary{ data: { 'gl-modal-action': 'delete',
- delete_user_url: admin_user_path(user),
- block_user_url: block_admin_user_path(user),
- username: sanitize_name(user.name) } }
- = s_('AdminUsers|Delete user')
- %li
- %button.js-delete-user-modal-button.gl-button.btn.btn-danger-tertiary{ data: { 'gl-modal-action': 'delete-with-contributions',
- delete_user_url: admin_user_path(user, hard_delete: true),
- block_user_url: block_admin_user_path(user),
- username: sanitize_name(user.name) } }
- = s_('AdminUsers|Delete user and contributions')
diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml
index e4438f38a47..1a43d91b800 100644
--- a/app/views/admin/users/_users.html.haml
+++ b/app/views/admin/users/_users.html.haml
@@ -73,20 +73,9 @@
= link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do
= title
-- if Feature.enabled?(:vue_admin_users, default_enabled: :yaml)
- #js-admin-users-app{ data: admin_users_data_attributes(@users) }
- .gl-spinner-container.gl-my-7
- %span.gl-vertical-align-bottom.gl-spinner.gl-spinner-dark.gl-spinner-lg{ aria: { label: _('Loading') } }
-- elsif @users.empty?
- .nothing-here-block.border-top-0
- = s_('AdminUsers|No users found')
-- else
- .table-holder
- .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' }
- - user_table_headers.each do |header|
- .table-section{ class: header[:section_class_name], role: 'rowheader' }= header[:header_text]
-
- = render partial: 'admin/users/user', collection: @users
+#js-admin-users-app{ data: admin_users_data_attributes(@users) }
+ .gl-spinner-container.gl-my-7
+ %span.gl-vertical-align-bottom.gl-spinner.gl-spinner-dark.gl-spinner-lg{ aria: { label: _('Loading') } }
= paginate_collection @users
diff --git a/app/workers/ssh_keys/expired_notification_worker.rb b/app/workers/ssh_keys/expired_notification_worker.rb
index 95c9601a04c..e8baf0c28dd 100644
--- a/app/workers/ssh_keys/expired_notification_worker.rb
+++ b/app/workers/ssh_keys/expired_notification_worker.rb
@@ -11,12 +11,31 @@ module SshKeys
tags :exclude_from_kubernetes
idempotent!
+ BATCH_SIZE = 500
+
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml)
- # rubocop:disable CodeReuse/ActiveRecord
- Key.expired_and_not_notified.each_batch(of: 1000) do |relation| # rubocop:disable Cop/InBatches
- users = User.where(id: relation.select(:user_id))
+ order = Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'expires_at_utc',
+ order_expression: Arel.sql("date(expires_at AT TIME ZONE 'UTC')").asc,
+ nullable: :not_nullable,
+ distinct: false,
+ add_to_projections: true
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'id',
+ order_expression: Key.arel_table[:id].asc
+ )
+ ])
+
+ scope = Key.expired_and_not_notified.order(order)
+
+ iterator = Gitlab::Pagination::Keyset::Iterator.new(scope: scope, use_union_optimization: true)
+ iterator.each_batch(of: BATCH_SIZE) do |relation|
+ users = User.where(id: relation.map(&:user_id)) # Keyset pagination will load the rows
users.each do |user|
with_context(user: user) do
@@ -24,7 +43,7 @@ module SshKeys
end
end
end
- # rubocop:enable CodeReuse/ActiveRecord
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/config/feature_flags/development/deployments_finder_implicitly_enforce_ordering_for_updated_at_filter.yml b/config/feature_flags/development/deployments_finder_implicitly_enforce_ordering_for_updated_at_filter.yml
deleted file mode 100644
index 68a3de44e88..00000000000
--- a/config/feature_flags/development/deployments_finder_implicitly_enforce_ordering_for_updated_at_filter.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: deployments_finder_implicitly_enforce_ordering_for_updated_at_filter
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59771
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329286
-milestone: '13.12'
-type: development
-group: group::release
-default_enabled: true
diff --git a/config/feature_flags/development/vue_admin_users.yml b/config/feature_flags/development/vue_admin_users.yml
deleted file mode 100644
index d7a37542cfd..00000000000
--- a/config/feature_flags/development/vue_admin_users.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: vue_admin_users
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48922
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290737
-milestone: '13.7'
-type: development
-group: group::compliance
-default_enabled: true
diff --git a/db/migrate/20210609090856_add_expiry_id_ssh_key_notification_index.rb b/db/migrate/20210609090856_add_expiry_id_ssh_key_notification_index.rb
new file mode 100644
index 00000000000..406bbe2095f
--- /dev/null
+++ b/db/migrate/20210609090856_add_expiry_id_ssh_key_notification_index.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddExpiryIdSshKeyNotificationIndex < ActiveRecord::Migration[6.1]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_keys_on_expires_at_and_id'
+
+ def up
+ add_concurrent_index :keys,
+ "date(timezone('UTC', expires_at)), id",
+ where: 'expiry_notification_delivered_at IS NULL',
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :keys, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210609090856 b/db/schema_migrations/20210609090856
new file mode 100644
index 00000000000..f0c3c25a01b
--- /dev/null
+++ b/db/schema_migrations/20210609090856
@@ -0,0 +1 @@
+597e04c51bdad1900b2535c9d664c9e3a4d2a5879e657ef470bbc7ac461d3cca \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5fa8419bd40..8a3da9ceed9 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -23615,6 +23615,8 @@ CREATE INDEX index_jira_tracker_data_on_service_id ON jira_tracker_data USING bt
CREATE INDEX index_keys_on_expires_at_and_expiry_notification_undelivered ON keys USING btree (date(timezone('UTC'::text, expires_at)), expiry_notification_delivered_at) WHERE (expiry_notification_delivered_at IS NULL);
+CREATE INDEX index_keys_on_expires_at_and_id ON keys USING btree (date(timezone('UTC'::text, expires_at)), id) WHERE (expiry_notification_delivered_at IS NULL);
+
CREATE UNIQUE INDEX index_keys_on_fingerprint ON keys USING btree (fingerprint);
CREATE INDEX index_keys_on_fingerprint_sha256 ON keys USING btree (fingerprint_sha256);
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 74e9eca443a..e75751a36bc 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -1544,15 +1544,6 @@ elements:
|:------------|:--------------------------------|:----------------------|
| _go to_ | making a browser go to location | "navigate to", "open" |
-### Permissions vs roles
-
-GitLab users are [assigned roles](../../../user/permissions.md) that confer specific permissions:
-
-- _Maintainer_ is an example of a role.
-- _Create new issue_ is an example of a permission.
-
-[Do not use](word_list.md) these terms interchangeably.
-
## GitLab versions
GitLab product documentation pages (not including [Contributor and Development](../../README.md)
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index ce10b11e90a..fd8766bbfb6 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -36,6 +36,13 @@ Try to avoid extra words when referring to an example or table in a documentatio
Do not use when talking about the product or its features. The documentation describes the product as it is today. ([Vale](../testing.md#vale) rule: [`CurrentStatus.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/CurrentStatus.yml))
+## Developer
+
+When writing about the Developer role, use a capital "D." Do not use the phrase, "if you are a developer"
+to mean someone who is assigned the Developer role. Instead, write it out. "If you are assigned the Developer role..."
+
+Do not use "Developer permissions." A user who is assigned the Developer role has a set of associated permissions.
+
## easily
Do not use. If the user doesn't find the process to be these things, we lose their trust.
@@ -52,6 +59,13 @@ When possible, use present tense instead. For example, use `after you execute th
Do not make possessive (GitLab's). This guidance follows [GitLab Brand Guidelines](https://about.gitlab.com/handbook/marketing/corporate-marketing/brand-activation/brand-guidelines/#trademark).
+## Guest
+
+When writing about the Guest role, use a capital "G." Do not use the phrase, "if you are a guest"
+to mean someone who is assigned the Guest role. Instead, write it out. "If you are assigned the Guest role..."
+
+Do not use "Guest permissions." A user who is assigned the Guest role has a set of associated permissions.
+
## handy
Do not use. If the user doesn't find the process to be these things, we lose their trust.
@@ -68,6 +82,13 @@ Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale
Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml))
+## Maintainer
+
+When writing about the Maintainer role, use a capital "M." Do not use the phrase, "if you are a maintainer"
+to mean someone who is assigned the Maintainer role. Instead, write it out. "If you are assigned the Maintainer role..."
+
+Do not use "Maintainer permissions." A user who is assigned the Maintainer role has a set of associated permissions.
+
## may, might
**Might** means something has the probability of occurring. **May** gives permission to do something. Consider **can** instead of **may**.
@@ -76,6 +97,13 @@ Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#v
Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml))
+## Owner
+
+When writing about the Owner role, use a capital "M." Do not use the phrase, "if you are an owner"
+to mean someone who is assigned the Owner role. Instead, write it out. "If you are assigned the Owner role..."
+
+Do not use "Owner permissions." A user who is assigned the Owner role has a set of associated permissions.
+
## permissions
Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions.
@@ -88,6 +116,13 @@ Do not use. For details, see the [Microsoft style guide](https://docs.microsoft.
Do not use. Doing so may negatively affect other users and contributors, which is contrary to the GitLab value of [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion).
+## Reporter
+
+When writing about the Reporter role, use a capital "R." Do not use the phrase, "if you are a reporter"
+to mean someone who is assigned the Reporter role. Instead, write it out. "If you are assigned the Reporter role..."
+
+Do not use "Reporter permissions." A user who is assigned the Reporter role has a set of associated permissions.
+
## roles
Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions.
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 0fda30be9ba..48d7d68996e 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -46,30 +46,12 @@ To enable container scanning in your pipeline, you need the following:
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
- Docker `18.09.03` or higher installed on the same computer as the runner. If you're using the
shared runners on GitLab.com, then this is already the case.
-- An image matching the [supported distributions](https://aquasecurity.github.io/trivy/latest/vuln-detection/os/)).
+- An image matching the [supported distributions](#supported-distributions).
- [Build and push](../../packages/container_registry/index.md#build-and-push-by-using-gitlab-cicd)
- your Docker image to your project's container registry. The name of the Docker image should use
- the following [predefined CI/CD variables](../../../ci/variables/predefined_variables.md):
-
- ```plaintext
- $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
- ```
-
- You can use these directly in your `.gitlab-ci.yml` file:
-
- ```yaml
- build:
- image: docker:19.03.12
- stage: build
- services:
- - docker:19.03.12-dind
- variables:
- IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
- script:
- - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- - docker build -t $IMAGE_TAG .
- - docker push $IMAGE_TAG
- ```
+ the Docker image to your project's container registry. If using a third-party container
+ registry, you might need to provide authentication credentials using the `DOCKER_USER` and
+ `DOCKER_PASSWORD` [configuration variables](#available-cicd-variables).
+- The name of the Docker image to scan, in the `DOCKER_IMAGE` [configuration variable](#available-cicd-variables).
## Configuration
@@ -80,6 +62,9 @@ How you enable container scanning depends on your GitLab version:
that comes with your GitLab installation.
- GitLab versions earlier than 11.9: Copy and use the job from the
[`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml).
+
+Other changes:
+
- GitLab 13.6 [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263482) better support for
[FIPS](https://csrc.nist.gov/publications/detail/fips/140/2/final) by upgrading the
`CS_MAJOR_VERSION` from `2` to `3`. Version `3` of the `container_scanning` Docker image uses
@@ -117,21 +102,14 @@ that you can download and analyze later. When downloading, you always receive th
artifact.
The following is a sample `.gitlab-ci.yml` that builds your Docker image, pushes it to the container
-registry, and scans the containers:
+registry, and scans the image:
```yaml
-variables:
- DOCKER_DRIVER: overlay2
-
-stages:
- - build
- - test
-
build:
- image: docker:stable
+ image: docker:latest
stage: build
services:
- - docker:19.03.12-dind
+ - docker:dind
variables:
IMAGE: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
script:
@@ -183,6 +161,12 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
| `DOCKERFILE_PATH` | `Dockerfile` | The path to the `Dockerfile` to use for generating remediations. By default, the scanner looks for a file named `Dockerfile` in the root directory of the project. You should configure this variable only if your `Dockerfile` is in a non-standard location, such as a subdirectory. See [Solutions for vulnerabilities](#solutions-for-vulnerabilities-auto-remediation) for more details. | All |
| `SECURE_LOG_LEVEL` | `info` | Set the minimum logging level. Messages of this logging level or higher are output. From highest to lowest severity, the logging levels are: `fatal`, `error`, `warn`, `info`, `debug`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10880) in GitLab 13.1. | All |
+### Supported distributions
+
+Support depends on the scanner:
+
+- [Trivy](https://aquasecurity.github.io/trivy/latest/vuln-detection/os/) (Default).
+
### Overriding the container scanning template
If you want to override the job definition (for example, to change properties like `variables`), you
diff --git a/lib/banzai/filter/references/label_reference_filter.rb b/lib/banzai/filter/references/label_reference_filter.rb
index bf6b3e47d3b..12afece6e53 100644
--- a/lib/banzai/filter/references/label_reference_filter.rb
+++ b/lib/banzai/filter/references/label_reference_filter.rb
@@ -8,21 +8,57 @@ module Banzai
self.reference_type = :label
self.object_class = Label
+ def parent_records(parent, ids)
+ return Label.none unless parent.is_a?(Project) || parent.is_a?(Group)
+
+ labels = find_labels(parent)
+ label_ids = ids.map {|y| y[:label_id]}.compact
+ label_names = ids.map {|y| y[:label_name]}.compact
+ id_relation = labels.where(id: label_ids)
+ label_relation = labels.where(title: label_names)
+
+ Label.from_union([id_relation, label_relation])
+ end
+
def find_object(parent_object, id)
- find_labels(parent_object).find(id)
+ key = reference_cache.records_per_parent[parent_object].keys.find do |k|
+ k[:label_id] == id[:label_id] || k[:label_name] == id[:label_name]
+ end
+
+ reference_cache.records_per_parent[parent_object][key] if key
+ end
+
+ # Transform a symbol extracted from the text to a meaningful value
+ #
+ # This method has the contract that if a string `ref` refers to a
+ # record `record`, then `parse_symbol(ref) == record_identifier(record)`.
+ #
+ # This contract is slightly broken here, as we only have either the label_id
+ # or the label_name, but not both. But below, we have both pieces of information.
+ # But it's accounted for in `find_object`
+ def parse_symbol(symbol, match_data)
+ { label_id: match_data[:label_id]&.to_i, label_name: match_data[:label_name]&.tr('"', '') }
+ end
+
+ # We assume that most classes are identifying records by ID.
+ #
+ # This method has the contract that if a string `ref` refers to a
+ # record `record`, then `class.parse_symbol(ref) == record_identifier(record)`.
+ # See note in `parse_symbol` above
+ def record_identifier(record)
+ { label_id: record.id, label_name: record.title }
end
def references_in(text, pattern = Label.reference_pattern)
labels = {}
- unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
- namespace = $~[:namespace]
- project = $~[:project]
- project_path = reference_cache.full_project_path(namespace, project)
- label = find_label_cached(project_path, $~[:label_id], $~[:label_name])
-
- if label
- labels[label.id] = yield match, label.id, project, namespace, $~
- "#{REFERENCE_PLACEHOLDER}#{label.id}"
+
+ unescaped_html = unescape_html_entities(text).gsub(pattern).with_index do |match, index|
+ ident = identifier($~)
+ label = yield match, ident, $~[:project], $~[:namespace], $~
+
+ if label != match
+ labels[index] = label
+ "#{REFERENCE_PLACEHOLDER}#{index}"
else
match
end
@@ -33,20 +69,6 @@ module Banzai
escape_with_placeholders(unescaped_html, labels)
end
- def find_label_cached(parent_ref, label_id, label_name)
- cached_call(:banzai_find_label_cached, label_name&.tr('"', '') || label_id, path: [object_class, parent_ref]) do
- find_label(parent_ref, label_id, label_name)
- end
- end
-
- def find_label(parent_ref, label_id, label_name)
- parent = parent_from_ref(parent_ref)
- return unless parent
-
- label_params = label_params(label_id, label_name)
- find_labels(parent).find_by(label_params)
- end
-
def find_labels(parent)
params = if parent.is_a?(Group)
{ group_id: parent.id,
@@ -60,21 +82,6 @@ module Banzai
LabelsFinder.new(nil, params).execute(skip_authorization: true)
end
- # Parameters to pass to `Label.find_by` based on the given arguments
- #
- # id - Integer ID to pass. If present, returns {id: id}
- # name - String name to pass. If `id` is absent, finds by name without
- # surrounding quotes.
- #
- # Returns a Hash.
- def label_params(id, name)
- if name
- { name: name.tr('"', '') }
- else
- { id: id.to_i }
- end
- end
-
def url_for_object(label, parent)
label_url_method =
if context[:label_url_method]
@@ -121,6 +128,14 @@ module Banzai
presenter = object.present(issuable_subject: project || group)
LabelsHelper.label_tooltip_title(presenter)
end
+
+ def parent
+ project || group
+ end
+
+ def requires_unescaping?
+ true
+ end
end
end
end
diff --git a/lib/banzai/filter/references/reference_cache.rb b/lib/banzai/filter/references/reference_cache.rb
index ab0c74e00d9..24b8b4984cd 100644
--- a/lib/banzai/filter/references/reference_cache.rb
+++ b/lib/banzai/filter/references/reference_cache.rb
@@ -29,15 +29,15 @@ module Banzai
refs = Hash.new { |hash, key| hash[key] = Set.new }
nodes.each do |node|
- node.to_html.scan(regex) do
- path = if parent_type == :project
- full_project_path($~[:namespace], $~[:project])
- else
- full_group_path($~[:group])
- end
+ prepare_node_for_scan(node).scan(regex) do
+ parent_path = if parent_type == :project
+ full_project_path($~[:namespace], $~[:project])
+ else
+ full_group_path($~[:group])
+ end
ident = filter.identifier($~)
- refs[path] << ident if ident
+ refs[parent_path] << ident if ident
end
end
@@ -55,9 +55,23 @@ module Banzai
@per_reference ||= {}
@per_reference[parent_type] ||= begin
- refs = references_per_parent.keys.to_set
+ refs = references_per_parent.keys
+ parent_ref = {}
- find_for_paths(refs.to_a).index_by(&:full_path)
+ # if we already have a parent, no need to query it again
+ refs.each do |ref|
+ next unless ref
+
+ if context[:project]&.full_path == ref
+ parent_ref[ref] = context[:project]
+ elsif context[:group]&.full_path == ref
+ parent_ref[ref] = context[:group]
+ end
+
+ refs -= [ref] if parent_ref[ref]
+ end
+
+ find_for_paths(refs).index_by(&:full_path).merge(parent_ref)
end
end
@@ -87,7 +101,7 @@ module Banzai
@_records_per_project[filter.object_class.to_s.underscore]
end
- def relation_for_paths(paths)
+ def objects_for_paths(paths)
klass = parent_type.to_s.camelize.constantize
result = klass.where_full_path_in(paths)
return result if parent_type == :group
@@ -102,7 +116,7 @@ module Banzai
to_query = paths - cache.keys
unless to_query.empty?
- records = relation_for_paths(to_query)
+ records = objects_for_paths(to_query)
found = []
records.each do |record|
@@ -119,7 +133,7 @@ module Banzai
cache.slice(*paths).values.compact
else
- relation_for_paths(paths)
+ objects_for_paths(paths)
end
end
@@ -170,6 +184,16 @@ module Banzai
def refs_cache
Gitlab::SafeRequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
end
+
+ def prepare_node_for_scan(node)
+ html = node.to_html
+
+ filter.requires_unescaping? ? unescape_html_entities(html) : html
+ end
+
+ def unescape_html_entities(text)
+ CGI.unescapeHTML(text.to_s)
+ end
end
end
end
diff --git a/lib/banzai/filter/references/reference_filter.rb b/lib/banzai/filter/references/reference_filter.rb
index 58436f4505e..6c2c993cc01 100644
--- a/lib/banzai/filter/references/reference_filter.rb
+++ b/lib/banzai/filter/references/reference_filter.rb
@@ -109,6 +109,10 @@ module Banzai
context[:group]
end
+ def requires_unescaping?
+ false
+ end
+
private
# Returns a data attribute String to attach to a reference link
diff --git a/scripts/verify-tff-mapping b/scripts/verify-tff-mapping
index e18a14e17d6..4555a9854dd 100755
--- a/scripts/verify-tff-mapping
+++ b/scripts/verify-tff-mapping
@@ -84,8 +84,8 @@ tests = [
{
explanation: 'FOSS views should map to respective spec',
- source: 'app/views/admin/users/_user.html.haml',
- expected: ['spec/views/admin/users/_user.html.haml_spec.rb']
+ source: 'app/views/admin/dashboard/index.html.haml',
+ expected: ['spec/views/admin/dashboard/index.html.haml_spec.rb']
},
{
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index 01341398135..3599658ee56 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -356,27 +356,19 @@ RSpec.describe 'Admin::Users::User' do
end
end
- [true, false].each do |vue_admin_users|
- context "with vue_admin_users feature flag set to #{vue_admin_users}", js: vue_admin_users do
- before do
- stub_feature_flags(vue_admin_users: vue_admin_users)
- end
-
- describe 'GET /admin/users' do
- context 'user pending approval' do
- it 'shows user info', :aggregate_failures do
- user = create(:user, :blocked_pending_approval)
+ describe 'GET /admin/users', :js do
+ context 'user pending approval' do
+ it 'shows user info', :aggregate_failures do
+ user = create(:user, :blocked_pending_approval)
- visit admin_users_path
- click_link 'Pending approval'
- click_link user.name
+ visit admin_users_path
+ click_link 'Pending approval'
+ click_link user.name
- expect(page).to have_content(user.name)
- expect(page).to have_content('Pending approval')
- expect(page).to have_link('Approve user')
- expect(page).to have_link('Reject request')
- end
- end
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('Pending approval')
+ expect(page).to have_link('Approve user')
+ expect(page).to have_link('Reject request')
end
end
end
diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb
index d3931373ee3..790c9a4fc74 100644
--- a/spec/features/admin/users/users_spec.rb
+++ b/spec/features/admin/users/users_spec.rb
@@ -11,291 +11,300 @@ RSpec.describe 'Admin::Users' do
gitlab_enable_admin_mode_sign_in(current_user)
end
- [true, false].each do |vue_admin_users|
- context "with vue_admin_users feature flag set to #{vue_admin_users}", js: vue_admin_users do
- before do
- stub_feature_flags(vue_admin_users: vue_admin_users)
- end
+ describe 'GET /admin/users', :js do
+ before do
+ visit admin_users_path
+ end
- describe 'GET /admin/users' do
- before do
- visit admin_users_path
- end
+ it "is ok" do
+ expect(current_path).to eq(admin_users_path)
+ end
- it "is ok" do
- expect(current_path).to eq(admin_users_path)
- end
+ it "has users list" do
+ current_user.reload
- it "has users list" do
- current_user.reload
+ expect(page).to have_content(current_user.email)
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y'))
+ expect(page).to have_content(user.email)
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('Projects')
- expect(page).to have_content(current_user.email)
- expect(page).to have_content(current_user.name)
- expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y'))
- expect(page).to have_content(user.email)
- expect(page).to have_content(user.name)
- expect(page).to have_content('Projects')
+ click_user_dropdown_toggle(user.id)
- click_user_dropdown_toggle(user.id)
+ expect(page).to have_button('Block')
+ expect(page).to have_button('Deactivate')
+ expect(page).to have_button('Delete user')
+ expect(page).to have_button('Delete user and contributions')
+ end
- expect(page).to have_button('Block')
- expect(page).to have_button('Deactivate')
- expect(page).to have_button('Delete user')
- expect(page).to have_button('Delete user and contributions')
- end
+ it 'clicking edit user takes us to edit page', :aggregate_failures do
+ page.within("[data-testid='user-actions-#{user.id}']") do
+ click_link 'Edit'
+ end
- it 'clicking edit user takes us to edit page', :aggregate_failures do
- page.within("[data-testid='user-actions-#{user.id}']") do
- click_link 'Edit'
- end
+ expect(page).to have_content('Name')
+ expect(page).to have_content('Password')
+ end
- expect(page).to have_content('Name')
- expect(page).to have_content('Password')
- end
+ describe 'view extra user information' do
+ it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
+ expect(page).not_to have_selector('#__BV_popover_1__')
- describe 'view extra user information' do
- it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
- expect(page).not_to have_selector('#__BV_popover_1__')
+ first_user_link = page.first('.js-user-link')
+ first_user_link.hover
- first_user_link = page.first('.js-user-link')
- first_user_link.hover
+ expect(page).to have_selector('#__BV_popover_1__')
+ end
+ end
- expect(page).to have_selector('#__BV_popover_1__')
- end
- end
+ context 'user project count' do
+ before do
+ project = create(:project)
+ project.add_maintainer(current_user)
+ end
+
+ it 'displays count of users projects' do
+ visit admin_users_path
- context 'user project count' do
- before do
- project = create(:project)
- project.add_maintainer(current_user)
- end
+ expect(page.find("[data-testid='user-project-count-#{current_user.id}']").text).to eq("1")
+ end
+ end
- it 'displays count of users projects' do
- visit admin_users_path
+ describe 'tabs' do
+ it 'has multiple tabs to filter users' do
+ expect(page).to have_link('Active', href: admin_users_path)
+ expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins'))
+ expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled'))
+ expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled'))
+ expect(page).to have_link('External', href: admin_users_path(filter: 'external'))
+ expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked'))
+ expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated'))
+ expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop'))
+ end
- expect(page.find("[data-testid='user-project-count-#{current_user.id}']").text).to eq("1")
- end
+ context '`Pending approval` tab' do
+ before do
+ visit admin_users_path
end
- describe 'tabs' do
- it 'has multiple tabs to filter users' do
- expect(page).to have_link('Active', href: admin_users_path)
- expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins'))
- expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled'))
- expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled'))
- expect(page).to have_link('External', href: admin_users_path(filter: 'external'))
- expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked'))
- expect(page).to have_link('Banned', href: admin_users_path(filter: 'banned'))
- expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated'))
- expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop'))
- end
-
- context '`Pending approval` tab' do
- before do
- visit admin_users_path
- end
-
- it 'shows the `Pending approval` tab' do
- expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
- end
- end
+ it 'shows the `Pending approval` tab' do
+ expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
end
+ end
+ end
+
+ describe 'search and sort' do
+ before_all do
+ create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago)
+ create(:user, name: 'Foo Baz', last_activity_on: 2.days.ago)
+ create(:user, name: 'Dmitriy')
+ end
- describe 'search and sort' do
- before_all do
- create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago)
- create(:user, name: 'Foo Baz', last_activity_on: 2.days.ago)
- create(:user, name: 'Dmitriy')
- end
+ it 'searches users by name' do
+ visit admin_users_path(search_query: 'Foo')
- it 'searches users by name' do
- visit admin_users_path(search_query: 'Foo')
+ expect(page).to have_content('Foo Bar')
+ expect(page).to have_content('Foo Baz')
+ expect(page).not_to have_content('Dmitriy')
+ end
- expect(page).to have_content('Foo Bar')
- expect(page).to have_content('Foo Baz')
- expect(page).not_to have_content('Dmitriy')
- end
+ it 'sorts users by name' do
+ visit admin_users_path
- it 'sorts users by name' do
- visit admin_users_path
+ sort_by('Name')
- sort_by('Name')
+ expect(first_row.text).to include('Dmitriy')
+ expect(second_row.text).to include('Foo Bar')
+ end
- expect(first_row.text).to include('Dmitriy')
- expect(second_row.text).to include('Foo Bar')
- end
+ it 'sorts search results only' do
+ visit admin_users_path(search_query: 'Foo')
- it 'sorts search results only' do
- visit admin_users_path(search_query: 'Foo')
+ sort_by('Name')
+ expect(page).not_to have_content('Dmitriy')
+ expect(first_row.text).to include('Foo Bar')
+ expect(second_row.text).to include('Foo Baz')
+ end
- sort_by('Name')
- expect(page).not_to have_content('Dmitriy')
- expect(first_row.text).to include('Foo Bar')
- expect(second_row.text).to include('Foo Baz')
- end
+ it 'searches with respect of sorting' do
+ visit admin_users_path(sort: 'Name')
+
+ fill_in :search_query, with: 'Foo'
+ click_button('Search users')
+
+ expect(first_row.text).to include('Foo Bar')
+ expect(second_row.text).to include('Foo Baz')
+ end
- it 'searches with respect of sorting' do
- visit admin_users_path(sort: 'Name')
+ it 'sorts users by recent last activity' do
+ visit admin_users_path(search_query: 'Foo')
- fill_in :search_query, with: 'Foo'
- click_button('Search users')
+ sort_by('Recent last activity')
- expect(first_row.text).to include('Foo Bar')
- expect(second_row.text).to include('Foo Baz')
- end
+ expect(first_row.text).to include('Foo Baz')
+ expect(second_row.text).to include('Foo Bar')
+ end
- it 'sorts users by recent last activity' do
- visit admin_users_path(search_query: 'Foo')
+ it 'sorts users by oldest last activity' do
+ visit admin_users_path(search_query: 'Foo')
- sort_by('Recent last activity')
+ sort_by('Oldest last activity')
- expect(first_row.text).to include('Foo Baz')
- expect(second_row.text).to include('Foo Bar')
- end
+ expect(first_row.text).to include('Foo Bar')
+ expect(second_row.text).to include('Foo Baz')
+ end
+ end
- it 'sorts users by oldest last activity' do
- visit admin_users_path(search_query: 'Foo')
+ describe 'Two-factor Authentication filters' do
+ it 'counts users who have enabled 2FA' do
+ create(:user, :two_factor)
- sort_by('Oldest last activity')
+ visit admin_users_path
- expect(first_row.text).to include('Foo Bar')
- expect(second_row.text).to include('Foo Baz')
- end
+ page.within('.filter-two-factor-enabled small') do
+ expect(page).to have_content('1')
end
+ end
- describe 'Two-factor Authentication filters' do
- it 'counts users who have enabled 2FA' do
- create(:user, :two_factor)
+ it 'filters by users who have enabled 2FA' do
+ user = create(:user, :two_factor)
- visit admin_users_path
+ visit admin_users_path
+ click_link '2FA Enabled'
- page.within('.filter-two-factor-enabled small') do
- expect(page).to have_content('1')
- end
- end
+ expect(page).to have_content(user.email)
+ end
- it 'filters by users who have enabled 2FA' do
- user = create(:user, :two_factor)
+ it 'counts users who have not enabled 2FA' do
+ visit admin_users_path
- visit admin_users_path
- click_link '2FA Enabled'
+ page.within('.filter-two-factor-disabled small') do
+ expect(page).to have_content('2') # Including admin
+ end
+ end
- expect(page).to have_content(user.email)
- end
+ it 'filters by users who have not enabled 2FA' do
+ visit admin_users_path
+ click_link '2FA Disabled'
- it 'counts users who have not enabled 2FA' do
- visit admin_users_path
+ expect(page).to have_content(user.email)
+ end
+ end
- page.within('.filter-two-factor-disabled small') do
- expect(page).to have_content('2') # Including admin
- end
- end
+ describe 'Pending approval filter' do
+ it 'counts users who are pending approval' do
+ create_list(:user, 2, :blocked_pending_approval)
- it 'filters by users who have not enabled 2FA' do
- visit admin_users_path
- click_link '2FA Disabled'
+ visit admin_users_path
- expect(page).to have_content(user.email)
- end
+ page.within('.filter-blocked-pending-approval small') do
+ expect(page).to have_content('2')
end
+ end
- describe 'Pending approval filter' do
- it 'counts users who are pending approval' do
- create_list(:user, 2, :blocked_pending_approval)
+ it 'filters by users who are pending approval' do
+ user = create(:user, :blocked_pending_approval)
- visit admin_users_path
+ visit admin_users_path
+ click_link 'Pending approval'
- page.within('.filter-blocked-pending-approval small') do
- expect(page).to have_content('2')
- end
- end
+ expect(page).to have_content(user.email)
+ end
+ end
- it 'filters by users who are pending approval' do
- user = create(:user, :blocked_pending_approval)
+ context 'when blocking/unblocking a user' do
+ it 'shows confirmation and allows blocking and unblocking', :js do
+ expect(page).to have_content(user.email)
- visit admin_users_path
- click_link 'Pending approval'
+ click_action_in_user_dropdown(user.id, 'Block')
- expect(page).to have_content(user.email)
- end
- end
+ wait_for_requests
+
+ expect(page).to have_content('Block user')
+ expect(page).to have_content('Blocking user has the following effects')
+ expect(page).to have_content('User will not be able to login')
+ expect(page).to have_content('Owned groups will be left')
- context 'when blocking/unblocking a user' do
- it 'shows confirmation and allows blocking and unblocking', :js do
- expect(page).to have_content(user.email)
+ find('.modal-footer button', text: 'Block').click
- click_action_in_user_dropdown(user.id, 'Block')
+ wait_for_requests
- wait_for_requests
+ expect(page).to have_content('Successfully blocked')
+ expect(page).not_to have_content(user.email)
- expect(page).to have_content('Block user')
- expect(page).to have_content('Blocking user has the following effects')
- expect(page).to have_content('User will not be able to login')
- expect(page).to have_content('Owned groups will be left')
+ click_link 'Blocked'
- find('.modal-footer button', text: 'Block').click
+ wait_for_requests
- wait_for_requests
+ expect(page).to have_content(user.email)
- expect(page).to have_content('Successfully blocked')
- expect(page).not_to have_content(user.email)
+ click_action_in_user_dropdown(user.id, 'Unblock')
- click_link 'Blocked'
+ expect(page).to have_content('Unblock user')
+ expect(page).to have_content('You can always block their account again if needed.')
- wait_for_requests
+ find('.modal-footer button', text: 'Unblock').click
- expect(page).to have_content(user.email)
+ wait_for_requests
- click_action_in_user_dropdown(user.id, 'Unblock')
+ expect(page).to have_content('Successfully unblocked')
+ expect(page).not_to have_content(user.email)
+ end
+ end
- expect(page).to have_content('Unblock user')
- expect(page).to have_content('You can always block their account again if needed.')
+ context 'when deactivating/re-activating a user' do
+ it 'shows confirmation and allows deactivating and re-activating', :js do
+ expect(page).to have_content(user.email)
- find('.modal-footer button', text: 'Unblock').click
+ click_action_in_user_dropdown(user.id, 'Deactivate')
- wait_for_requests
+ expect(page).to have_content('Deactivate user')
+ expect(page).to have_content('Deactivating a user has the following effects')
+ expect(page).to have_content('The user will be logged out')
+ expect(page).to have_content('Personal projects, group and user history will be left intact')
- expect(page).to have_content('Successfully unblocked')
- expect(page).not_to have_content(user.email)
- end
- end
+ find('.modal-footer button', text: 'Deactivate').click
- context 'when deactivating/re-activating a user' do
- it 'shows confirmation and allows deactivating and re-activating', :js do
- expect(page).to have_content(user.email)
+ wait_for_requests
- click_action_in_user_dropdown(user.id, 'Deactivate')
+ expect(page).to have_content('Successfully deactivated')
+ expect(page).not_to have_content(user.email)
- expect(page).to have_content('Deactivate user')
- expect(page).to have_content('Deactivating a user has the following effects')
- expect(page).to have_content('The user will be logged out')
- expect(page).to have_content('Personal projects, group and user history will be left intact')
+ click_link 'Deactivated'
- find('.modal-footer button', text: 'Deactivate').click
+ wait_for_requests
- wait_for_requests
+ expect(page).to have_content(user.email)
- expect(page).to have_content('Successfully deactivated')
- expect(page).not_to have_content(user.email)
+ click_action_in_user_dropdown(user.id, 'Activate')
- click_link 'Deactivated'
+ expect(page).to have_content('Activate user')
+ expect(page).to have_content('You can always deactivate their account again if needed.')
- wait_for_requests
+ find('.modal-footer button', text: 'Activate').click
- expect(page).to have_content(user.email)
+ wait_for_requests
- click_action_in_user_dropdown(user.id, 'Activate')
+ expect(page).to have_content('Successfully activated')
+ expect(page).not_to have_content(user.email)
+ end
+ end
- expect(page).to have_content('Activate user')
- expect(page).to have_content('You can always deactivate their account again if needed.')
+ describe 'internal users' do
+ context 'when showing a `Ghost User`' do
+ let_it_be(:ghost_user) { create(:user, :ghost) }
- find('.modal-footer button', text: 'Activate').click
+ it 'does not render actions dropdown' do
+ expect(page).not_to have_css("[data-testid='user-actions-#{ghost_user.id}'] [data-testid='dropdown-toggle']")
+ end
+ end
- wait_for_requests
+ context 'when showing a `Bot User`' do
+ let_it_be(:bot_user) { create(:user, user_type: :alert_bot) }
- expect(page).to have_content('Successfully activated')
- expect(page).not_to have_content(user.email)
- end
+ it 'does not render actions dropdown' do
+ expect(page).not_to have_css("[data-testid='user-actions-#{bot_user.id}'] [data-testid='dropdown-toggle']")
end
end
end
diff --git a/spec/finders/deployments_finder_spec.rb b/spec/finders/deployments_finder_spec.rb
index b294f1117f5..bd03b254f40 100644
--- a/spec/finders/deployments_finder_spec.rb
+++ b/spec/finders/deployments_finder_spec.rb
@@ -81,16 +81,6 @@ RSpec.describe DeploymentsFinder do
it 'returns deployments with matched updated_at' do
is_expected.to match_array([deployment_2, deployment_1])
end
-
- context 'when deployments_finder_implicitly_enforce_ordering_for_updated_at_filter feature flag is disabled' do
- before do
- stub_feature_flags(deployments_finder_implicitly_enforce_ordering_for_updated_at_filter: false)
- end
-
- it 'returns deployments with matched updated_at' do
- is_expected.to match_array([deployment_1, deployment_2])
- end
- end
end
context 'when the environment name is specified' do
@@ -244,20 +234,6 @@ RSpec.describe DeploymentsFinder do
expect(subject.order_values.first.to_sql).to eq(Deployment.arel_table[:updated_at].asc.to_sql)
expect(subject.order_values.second.to_sql).to eq(Deployment.arel_table[:id].asc.to_sql)
end
-
- context 'when deployments_finder_implicitly_enforce_ordering_for_updated_at_filter feature flag is disabled' do
- before do
- stub_feature_flags(deployments_finder_implicitly_enforce_ordering_for_updated_at_filter: false)
- end
-
- it 'sorts by only one column' do
- expect(subject.order_values.size).to eq(1)
- end
-
- it 'sorts by `id`' do
- expect(subject.order_values.first.to_sql).to eq(Deployment.arel_table[:id].asc.to_sql)
- end
- end
end
context 'when filtering by finished time' do
diff --git a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
index db7dda96cad..b18d68c8dd4 100644
--- a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
@@ -702,4 +702,72 @@ RSpec.describe Banzai::Filter::References::LabelReferenceFilter do
expect(result.css('a').first.text).to eq "#{label.name} in #{project.full_name}"
end
end
+
+ context 'checking N+1' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project) { create(:project, :public, namespace: group) }
+ let_it_be(:project2) { create(:project, :public, namespace: group2) }
+ let_it_be(:project3) { create(:project, :public) }
+ let_it_be(:project_label) { create(:label, project: project) }
+ let_it_be(:project_label2) { create(:label, project: project) }
+ let_it_be(:project2_label) { create(:label, project: project2) }
+ let_it_be(:group2_label) { create(:group_label, group: group2, color: '#00ff00') }
+ let_it_be(:project_reference) { "#{project_label.to_reference}" }
+ let_it_be(:project_reference2) { "#{project_label2.to_reference}" }
+ let_it_be(:project2_reference) { "#{project2_label.to_reference}" }
+ let_it_be(:group2_reference) { "#{project2.full_path}~#{group2_label.name}" }
+
+ it 'does not have N+1 per multiple references per project', :use_sql_query_cache do
+ markdown = "#{project_reference}"
+ control_count = 1
+
+ expect do
+ reference_filter(markdown)
+ end.not_to exceed_all_query_limit(control_count)
+
+ markdown = "#{project_reference} ~qwert ~werty ~ertyu ~rtyui #{project_reference2}"
+
+ expect do
+ reference_filter(markdown)
+ end.not_to exceed_all_query_limit(control_count)
+ end
+
+ it 'has N+1 for multiple unique project/group references', :use_sql_query_cache do
+ # reference to already loaded project, only one query
+ markdown = "#{project_reference}"
+ control_count = 1
+
+ expect do
+ reference_filter(markdown, project: project)
+ end.not_to exceed_all_query_limit(control_count)
+
+ # Since we're not batching label queries across projects/groups,
+ # queries increase when a new project/group is added.
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
+ # first reference to already loaded project (1),
+ # second reference requires project and namespace (2), and label (1)
+ markdown = "#{project_reference} #{group2_reference}"
+ max_count = control_count + 3
+
+ expect do
+ reference_filter(markdown)
+ end.not_to exceed_all_query_limit(max_count)
+
+ # third reference to already queried project/namespace, nothing extra (no N+1 here)
+ markdown = "#{project_reference} #{group2_reference} #{project2_reference}"
+
+ expect do
+ reference_filter(markdown)
+ end.not_to exceed_all_query_limit(max_count)
+
+ # last reference needs another namespace and label query (2)
+ markdown = "#{project_reference} #{group2_reference} #{project2_reference} #{project3.full_path}~test_label"
+ max_count += 2
+
+ expect do
+ reference_filter(markdown)
+ end.not_to exceed_all_query_limit(max_count)
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb
index 9e2a6f35910..c9404c381d3 100644
--- a/spec/lib/banzai/filter/references/reference_cache_spec.rb
+++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb
@@ -55,11 +55,12 @@ RSpec.describe Banzai::Filter::References::ReferenceCache do
cache_single.load_records_per_parent
end.count
+ expect(control_count).to eq 1
+
# Since this is an issue filter that is not batching issue queries
# across projects, we have to account for that.
- # 1 for both projects, 1 for issues in each project == 3
- # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
- max_count = control_count + 1
+ # 1 for original issue, 2 for second route/project, 1 for other issue
+ max_count = control_count + 3
expect do
cache.load_references_per_parent(filter.nodes)
diff --git a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
index 7ab3b24b1c2..2e324669870 100644
--- a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
@@ -233,13 +233,15 @@ RSpec.describe Banzai::Filter::References::SnippetReferenceFilter do
reference_filter(markdown)
end.count
+ expect(control_count).to eq 1
+
markdown = "#{reference} $9999990 $9999991 $9999992 $9999993 #{reference2} something/cool$12"
# Since we're not batching snippet queries across projects,
# we have to account for that.
# 1 for both projects, 1 for snippets in each project == 3
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
- max_count = control_count + 1
+ max_count = control_count + 2
expect do
reference_filter(markdown)
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index bc5e6ea7bb3..9f6851ecfbd 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -600,9 +600,8 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
setup_import_export_config('light')
setup_reader(reader)
- expect(project)
- .to receive(:merge_requests)
- .and_raise(exception)
+ expect(project).to receive(:merge_requests).and_call_original
+ expect(project).to receive(:merge_requests).and_raise(exception)
end
it 'report post import error' do
@@ -618,12 +617,9 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
setup_import_export_config('light')
setup_reader(reader)
- expect(project)
- .to receive(:merge_requests)
- .and_raise(exception)
- expect(project)
- .to receive(:merge_requests)
- .and_call_original
+ expect(project).to receive(:merge_requests).and_call_original
+ expect(project).to receive(:merge_requests).and_raise(exception)
+ expect(project).to receive(:merge_requests).and_call_original
expect(restored_project_json).to eq(true)
end
diff --git a/spec/views/admin/users/_user.html.haml_spec.rb b/spec/views/admin/users/_user.html.haml_spec.rb
deleted file mode 100644
index aed05e4ea9b..00000000000
--- a/spec/views/admin/users/_user.html.haml_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'admin/users/_user.html.haml' do
- before do
- allow(view).to receive(:user).and_return(user)
- end
-
- context 'internal users' do
- context 'when showing a `Ghost User`' do
- let(:user) { create(:user, :ghost) }
-
- it 'does not render action buttons' do
- render
-
- expect(rendered).not_to have_selector('.table-action-buttons')
- end
- end
-
- context 'when showing a `Bot User`' do
- let(:user) { create(:user, user_type: :alert_bot) }
-
- it 'does not render action buttons' do
- render
-
- expect(rendered).not_to have_selector('.table-action-buttons')
- end
- end
-
- context 'when showing a `Migration User`' do
- let(:user) { create(:user, user_type: :migration_bot) }
-
- it 'does not render action buttons' do
- render
-
- expect(rendered).not_to have_selector('.table-action-buttons')
- end
- end
- end
-
- context 'when showing an external user' do
- let(:user) { create(:user) }
-
- it 'renders action buttons' do
- render
-
- expect(rendered).to have_selector('.table-action-buttons')
- end
- end
-end
diff --git a/spec/workers/ssh_keys/expired_notification_worker_spec.rb b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
index 0fc999a81d6..84f8f8d7b83 100644
--- a/spec/workers/ssh_keys/expired_notification_worker_spec.rb
+++ b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
@@ -15,6 +15,20 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
describe '#perform' do
let_it_be(:user) { create(:user) }
+ context 'with a large batch' do
+ before do
+ stub_const("SshKeys::ExpiredNotificationWorker::BATCH_SIZE", 5)
+ end
+
+ let_it_be_with_reload(:keys) { create_list(:key, 20, expires_at: 3.days.ago, user: user) }
+
+ it 'updates all keys regardless of batch size' do
+ worker.perform
+
+ expect(keys.pluck(:expiry_notification_delivered_at)).not_to include(nil)
+ end
+ end
+
context 'with expiring key today' do
let_it_be_with_reload(:expired_today) { create(:key, expires_at: Time.current, user: user) }
diff --git a/vendor/project_templates/cluster_management.tar.gz b/vendor/project_templates/cluster_management.tar.gz
index f4afa928d1c..d24470f1642 100644
--- a/vendor/project_templates/cluster_management.tar.gz
+++ b/vendor/project_templates/cluster_management.tar.gz
Binary files differ