Commit 61130ebf authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents cef7d6d9 714d75ef
......@@ -19,7 +19,10 @@ export default {
<div class="mr-widget-body mr-state-locked media">
<status-icon status="loading" />
<div class="media-body">
<h4>{{ s__('mrWidget|This merge request is in the process of being merged') }}</h4>
<h4>
{{ s__('mrWidget|Merging! Drum roll, please…') }}
<gl-emoji data-name="drum" />
</h4>
<section class="mr-info-list">
<p>
{{ s__('mrWidget|The changes will be merged into') }}
......
......@@ -12,6 +12,7 @@ module ServiceParams
:bamboo_url,
:branches_to_be_notified,
:labels_to_be_notified,
:labels_to_be_notified_behavior,
:build_key,
:build_type,
:ca_pem,
......
......@@ -15,9 +15,14 @@ class ChatNotificationService < Service
EVENT_CHANNEL = proc { |event| "#{event}_channel" }
LABEL_NOTIFICATION_BEHAVIOURS = [
MATCH_ANY_LABEL = 'match_any',
MATCH_ALL_LABELS = 'match_all'
].freeze
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified
prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified, :labels_to_be_notified_behavior
# Custom serialized properties initialization
prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
......@@ -25,12 +30,14 @@ class ChatNotificationService < Service
boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, public_url: true, if: :activated?
validates :labels_to_be_notified_behavior, inclusion: { in: LABEL_NOTIFICATION_BEHAVIOURS }, allow_nil: true
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
self.branches_to_be_notified = "default"
self.labels_to_be_notified_behavior = MATCH_ANY_LABEL
elsif !self.notify_only_default_branch.nil?
# In older versions, there was only a boolean property named
# `notify_only_default_branch`. Now we have a string property named
......@@ -65,7 +72,20 @@ class ChatNotificationService < Service
{ type: 'text', name: 'username', placeholder: 'GitLab-integration' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'Do not send notifications for successful pipelines.' }.freeze,
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze,
{ type: 'text', name: 'labels_to_be_notified', placeholder: '~backend,~frontend', help: 'Send notifications for issue, merge request, and comment events with the listed labels only. Leave blank to receive notifications for all events.' }.freeze
{
type: 'text',
name: 'labels_to_be_notified',
placeholder: '~backend,~frontend',
help: 'Send notifications for issue, merge request, and comment events with the listed labels only. Leave blank to receive notifications for all events.'
}.freeze,
{
type: 'select',
name: 'labels_to_be_notified_behavior',
choices: [
['Match any of the labels', MATCH_ANY_LABEL],
['Match all of the labels', MATCH_ALL_LABELS]
]
}.freeze
].freeze
end
......@@ -136,11 +156,17 @@ class ChatNotificationService < Service
def notify_label?(data)
return true unless SUPPORTED_EVENTS_FOR_LABEL_FILTER.include?(data[:object_kind]) && labels_to_be_notified.present?
issue_labels = data.dig(:issue, :labels) || []
merge_request_labels = data.dig(:merge_request, :labels) || []
label_titles = (issue_labels + merge_request_labels).pluck(:title)
labels = data.dig(:issue, :labels) || data.dig(:merge_request, :labels)
return false if labels.nil?
(labels_to_be_notified_list & label_titles).any?
matching_labels = labels_to_be_notified_list & labels.pluck(:title)
if labels_to_be_notified_behavior == MATCH_ALL_LABELS
labels_to_be_notified_list.difference(matching_labels).empty?
else
matching_labels.any?
end
end
def user_id_from_hook_data(data)
......
......@@ -16,6 +16,7 @@ module ApplicationWorker
included do
set_queue
after_set_class_attribute { set_queue }
def structured_payload(payload = {})
context = Gitlab::ApplicationContext.current.merge(
......@@ -47,22 +48,14 @@ module ApplicationWorker
class_methods do
def inherited(subclass)
subclass.set_queue
subclass.after_set_class_attribute { subclass.set_queue }
end
def set_queue
queue_name = [queue_namespace, base_queue_name].compact.join(':')
queue_name = ::Gitlab::SidekiqConfig::WorkerRouter.global.route(self)
sidekiq_options queue: queue_name # rubocop:disable Cop/SidekiqOptionsQueue
end
def base_queue_name
name
.sub(/\AGitlab::/, '')
.sub(/Worker\z/, '')
.underscore
.tr('/', '_')
end
def queue_namespace(new_namespace = nil)
if new_namespace
sidekiq_options queue_namespace: new_namespace
......
......@@ -36,13 +36,13 @@ module WorkerAttributes
def feature_category(value, *extras)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
class_attributes[:feature_category] = value
set_class_attribute(:feature_category, value)
end
# Special case: mark this work as not associated with a feature category
# this should be used for cross-cutting concerns, such as mailer workers.
def feature_category_not_owned!
class_attributes[:feature_category] = :not_owned
set_class_attribute(:feature_category, :not_owned)
end
def get_feature_category
......@@ -64,7 +64,7 @@ module WorkerAttributes
def urgency(urgency)
raise "Invalid urgency: #{urgency}" unless VALID_URGENCIES.include?(urgency)
class_attributes[:urgency] = urgency
set_class_attribute(:urgency, urgency)
end
def get_urgency
......@@ -75,8 +75,8 @@ module WorkerAttributes
raise ArgumentError, "Invalid data consistency: #{data_consistency}" unless VALID_DATA_CONSISTENCIES.include?(data_consistency)
raise ArgumentError, 'Data consistency is already set' if class_attributes[:data_consistency]
class_attributes[:data_consistency_feature_flag] = feature_flag if feature_flag
class_attributes[:data_consistency] = data_consistency
set_class_attribute(:data_consistency_feature_flag, feature_flag) if feature_flag
set_class_attribute(:data_consistency, data_consistency)
validate_worker_attributes!
end
......@@ -105,7 +105,7 @@ module WorkerAttributes
# doc/development/sidekiq_style_guide.md#jobs-with-external-dependencies for
# details
def worker_has_external_dependencies!
class_attributes[:external_dependencies] = true
set_class_attribute(:external_dependencies, true)
end
# Returns a truthy value if the worker has external dependencies.
......@@ -118,7 +118,7 @@ module WorkerAttributes
def worker_resource_boundary(boundary)
raise "Invalid boundary" unless VALID_RESOURCE_BOUNDARIES.include? boundary
class_attributes[:resource_boundary] = boundary
set_class_attribute(:resource_boundary, boundary)
end
def get_worker_resource_boundary
......@@ -126,7 +126,7 @@ module WorkerAttributes
end
def idempotent!
class_attributes[:idempotent] = true
set_class_attribute(:idempotent, true)
validate_worker_attributes!
end
......@@ -136,7 +136,7 @@ module WorkerAttributes
end
def weight(value)
class_attributes[:weight] = value
set_class_attribute(:weight, value)
end
def get_weight
......@@ -146,7 +146,7 @@ module WorkerAttributes
end
def tags(*values)
class_attributes[:tags] = values
set_class_attribute(:tags, values)
end
def get_tags
......@@ -154,8 +154,8 @@ module WorkerAttributes
end
def deduplicate(strategy, options = {})
class_attributes[:deduplication_strategy] = strategy
class_attributes[:deduplication_options] = options
set_class_attribute(:deduplication_strategy, strategy)
set_class_attribute(:deduplication_options, options)
end
def get_deduplicate_strategy
......@@ -168,7 +168,7 @@ module WorkerAttributes
end
def big_payload!
class_attributes[:big_payload] = true
set_class_attribute(:big_payload, true)
end
def big_payload?
......
---
title: Improve merge message UI text
merge_request: 59693
author:
type: changed
---
title: Add options for Slack and Mattermost label filter behavior
merge_request: 56657
author:
type: added
---
title: Implement Sidekiq queue re-routing in the application
merge_request: 59604
author:
type: added
......@@ -438,6 +438,12 @@ production: &base
## Sidekiq
sidekiq:
log_format: json # (default is the original format)
# An array of tuples indicating the rules for re-routing a worker to a
# desirable queue before scheduling. For example:
# routing_rules:
# - ["resource_boundary=cpu", "cpu_boundary"]
# - ["feature_category=pages", null]
# - ["*", "default"]
## Auxiliary jobs
# Periodically executed jobs, to self-heal GitLab, do external synchronizations, etc.
......
......@@ -698,6 +698,7 @@ end
#
Settings['sidekiq'] ||= Settingslogic.new({})
Settings['sidekiq']['log_format'] ||= 'default'
Settings['sidekiq']['routing_rules'] ||= []
#
# GitLab Shell
......
......@@ -48,6 +48,7 @@ exceptions:
- EOL
- EXIF
- FAQ
- FIPS
- FOSS
- FQDN
- FREE
......
......@@ -208,6 +208,7 @@ In these cases, use the following workflow:
- [Image scaling guide](image_scaling.md)
- [Export to CSV](export_csv.md)
- [Cascading Settings](cascading_settings.md)
- [FIPS compliance](fips_compliance.md)
## Performance guides
......
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# FIPS compliance
FIPS is short for "Federal Information Processing Standard", a document which
defines certain security practices for a "cryptographic module" (CM). It aims
to ensure a certain security floor is met by vendors selling products to U.S.
Federal institutions.
WARNING:
GitLab is not FIPS compliant, even when built and run on a FIPS-enforcing
system. Large parts of the build are broken, and many features use forbidden
cryptographic primitives. Running GitLab on a FIPS-enforcing system is not
supported and may result in data loss. This document is intended to help
engineers looking to develop FIPS-related fixes. It is not intended to be used
to run a production GitLab instance.
There are two current FIPS standards: [140-2](https://en.wikipedia.org/wiki/FIPS_140-2)
and [140-3](https://en.wikipedia.org/wiki/FIPS_140-3). At GitLab we usually
mean FIPS 140-2.
## Current status
GitLab Inc has not committed to making GitLab FIPS-compliant at this time. We are
performing initial investigations to see how much work such an effort would be.
Read [Epic &5104](https://gitlab.com/groups/gitlab-org/-/epics/5104) for more
information on the status of the investigation.
## FIPS compliance at GitLab
In a FIPS context, compliance is a form of self-certification - if we say we are
"FIPS compliant", we mean that we *believe* we are. There are no external
certifications to acquire, but if we are aware of non-compliant areas
in GitLab, we cannot self-certify in good faith.
The known areas of non-compliance are tracked in [Epic &5104](https://gitlab.com/groups/gitlab-org/-/epics/5104).
To be compliant, all components (GitLab itself, Gitaly, etc) must be compliant,
along with the communication between those components, and any storage used by
them. Where functionality cannot be brought into compliance, it must be disabled
when FIPS mode is enabled.
## FIPS validation at GitLab
Unlike FIPS compliance, FIPS validation is a formal declaration of compliance by
an accredited auditor. The requirements needed to pass the audit are the same as
for FIPS compliance.
A list of FIPS-validated modules can be found at the
NIST (National Institute of Standards and Technology)
[cryptographic module validation program](https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules).
## Setting up a FIPS-enabled development environment
The simplest approach is to set up a virtual machine running
[Red Hat Enterprise Linux 8](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/security_hardening/using-the-system-wide-cryptographic-policies_security-hardening#switching-the-system-to-fips-mode_using-the-system-wide-cryptographic-policies).
Red Hat provide free licenses to developers, and permit the CD image to be
downloaded from the [Red Hat developer's portal](https://developers.redhat.com).
Registration is required.
After the virtual machine is set up, you can follow the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit)
installation instructions, including the [advanced instructions for RHEL](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/advanced.md#red-hat-enterprise-linux).
Note that `asdf` is not used for dependency management because it's essential to
use the RedHat-provided Go compiler and other system dependencies.
### Working around broken frontend asset compilation
A known bug affects asset compilation with FIPS mode enabled: [issue #322883](https://gitlab.com/gitlab-org/gitlab/-/issues/322883).
Until this is resolved, working on frontend issues is not feasible. We can still
work on backend issues by compiling the assets while FIPS is disabled, and
placing GDK into [static asset mode](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/configuration.md#webpack-settings):
1. Modify your `gdk.yml` to contain the following:
```yaml
webpack:
host: 127.0.0.1
port: 3808
static: true
```
1. In the GitLab repository, apply this patch to prevent the assets from being
automatically deleted whenever GDK is restarted:
```diff
diff --git a/scripts/frontend/webpack_dev_server.js b/scripts/frontend/webpack_dev_server.js
index fbb80c9617d..114720d457c 100755
--- a/scripts/frontend/webpack_dev_server.js
+++ b/scripts/frontend/webpack_dev_server.js
@@ -15,7 +15,7 @@ const baseConfig = {
// run webpack in compile-once mode and watch for changes
if (STATIC_MODE) {
nodemon({
- exec: `rm -rf public/assets/webpack ; yarn run webpack && exec ruby -run -e httpd public/ -p ${DEV_SERVER_PORT}`,
+ exec: `ruby -run -e httpd public/ -p ${DEV_SERVER_PORT}`,
watch: [
'config/webpack.config.js',
'app/assets/javascripts',
```
1. Run this command in the GitLab repository to generate the asset files
to be served:
```shell
bin/rails gitlab:assets:compile
```
Every time you change a frontend asset, you must re-run this command
(with FIPS mode disabled) before seeing the changes.
### Enable FIPS mode
After the assets are generated, run this command (as root) and restart the
virtual machine:
```shell
fips-mode-setup --enable
```
You can check whether it's taken effect by running:
```shell
fips-mode-setup --check
```
In this environment, OpenSSL refuses to perform cryptographic operations
forbidden by the FIPS standards. This enables you to reproduce FIPS-related bugs,
and validate fixes.
You should be able to open a web browser inside the virtual machine and log in
to the GitLab instance.
You can disable FIPS mode again by running this command, then restarting the
virtual machine:
```shell
fips-mode-setup --disable
```
......@@ -178,10 +178,132 @@ The correct approach is to add a new metric for GitLab 12.6 release with updated
and update existing business analysis artefacts to use `example_metric_without_archived` instead of `example_metric`
### 3. Metrics deprecation and removal
### 3. Deprecate a metric
The process for deprecating and removing metrics is under development. For
more information, see the following [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/284637).
If a metric is obsolete and you no longer use it, you can mark it as deprecated.
For an example of the metric deprecation process take a look at this [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59899)
To deprecate a metric:
1. Check the following YAML files and verify the metric is not used in an aggregate:
- [`config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/aggregates/)
- [`ee/config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/aggregates/)
1. Create an issue in the [GitLab Data Team
project](https://gitlab.com/gitlab-data/analytics/-/issues). Ask for
confirmation that the metric is not used by other teams, or in any of the SiSense
dashboards.
1. Verify the metric is not used to calculate the conversational index. The
conversational index is a measure that reports back to self-managed instances
to inform administrators of the progress of DevOps adoption for the instance.
You can check
[`CalculateConvIndexService`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/app/services/calculate_conv_index_service.rb)
to view the metrics that are used. The metrics are represented
as the keys that are passed as a field argument into the `get_value` method.
1. Document the deprecation in the metric's YAML definition. Set
the `status:` attribute to `deprecated`, for example:
```yaml
---
key_path: analytics_unique_visits.analytics_unique_visits_for_any_target_monthly
description: Visits to any of the pages listed above per month
product_section: dev
product_stage: manage
product_group: group::analytics
product_category:
value_type: number
status: deprecated
time_frame: 28d
data_source:
distribution:
- ce
tier:
- free
```
1. Replace the metric's instrumentation with a fixed value. This avoids wasting
resources to calculate the deprecated metric. In
[`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb)
or
[`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb),
replace the code that calculates the metric's value with a fixed value that
indicates it's deprecated:
```ruby
module Gitlab
class UsageData
DEPRECATED_VALUE = -1000
def analytics_unique_visits_data
results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
results['analytics_unique_visits_for_any_target_monthly'] = DEPRECATED_VALUE
{ analytics_unique_visits: results }
end
# ...
end
end
```
1. Update the Metrics Dictionary following [guidelines instructions](dictionary.md).
### 4. Remove a metric
Only deprecated metrics can be removed from Usage Ping.
For an example of the metric removal process take a look at this [example issue](https://gitlab.com/gitlab-org/gitlab/-/issues/297029)
To remove a deprecated metric:
1. Verify that removing the metric from the Usage Ping payload does not cause
errors in [Version App](https://gitlab.com/gitlab-services/version-gitlab-com)
when the updated payload is collected and processed. Version App collects
and persists all Usage Ping reports. To do that you can modify
[fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540)
used to test
[`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75)
endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload.
1. Create an issue in the
[GitLab Data Team project](https://gitlab.com/gitlab-data/analytics/-/issues).
Ask for confirmation that the metric is not referred to in any SiSense dashboards and
can be safely removed from Usage Ping. Use this
[example issue](https://gitlab.com/gitlab-data/analytics/-/issues/7539) for guidance.
This step can be skipped if verification done during [deprecation process](#3-deprecate-a-metric)
reported that metric is not required by any data transformation in Snowflake data warehouse nor it is
used by any of SiSense dashboards.
1. After you verify the metric can be safely removed,
update the attributes of the metric's YAML definition:
- Set the `status:` to `removed`.
- Set `milestone_removed:` to the number of the
milestone in which the metric was removed.
Do not remove the metric's YAML definition altogether. Some self-managed
instances might not immediately update to the latest version of GitLab, and
therefore continue to report the removed metric. The Product Intelligence team
requires a record of all removed metrics in order to identify and filter them.
For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#b01f429a54843feb22265100c0e4fec1b7da1240_10_10).
1. After you verify the metric can be safely removed,
remove the metric's instrumentation from
[`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb)
or
[`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb).
For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#6335dc533bd21df26db9de90a02dd66278c2390d_167_167).
1. Remove any other records related to the metric:
- The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags).
- The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events).
1. Update the Metrics Dictionary following [guidelines instructions](dictionary.md).
## Implementing Usage Ping
......
......@@ -48,10 +48,16 @@ notification. You do not need to add the hash sign (`#`).
Then fill in the integration configuration:
| Field | Description |
| ----- | ----------- |
| **Webhook** | The incoming webhook URL on Mattermost, similar to: `http://mattermost.example/hooks/5xo…`. |
| **Username** | (Optional) The username to show on messages sent to Mattermost. Fill this in to change the username of the bot. |
| **Notify only broken pipelines** | If you enable the **Pipeline** event and you want to be notified about failed pipelines only. |
| **Branches to be notified** | Select which branches to send notifications for. |
| **Labels to be notified** | (Optional) Labels that the issue or merge request must have to trigger a notification. Leave blank to get notifications for all issues and merge requests. |
- **Webhook**: The incoming webhook URL on Mattermost, similar to
`http://mattermost.example/hooks/5xo…`.
- **Username**: (Optional) The username shown in messages sent to Mattermost.
To change the bot's username, provide a value.
- **Notify only broken pipelines**: If you enable the **Pipeline** event, and you want
notifications about failed pipelines only.
- **Branches to be notified**: The branches to send notifications for.
- **Labels to be notified**: (Optional) Labels required for the issue or merge request
to trigger a notification. Leave blank to notify for all issues and merge requests.
- **Labels to be notified behavior**: When you use the **Labels to be notified** filter,
messages are sent when an issue or merge request contains _any_ of the labels specified
in the filter. You can also choose to trigger messages only when the issue or merge request
contains _all_ the labels defined in the filter.
......@@ -49,6 +49,7 @@ module Gitlab
def data
{
gitlab_version: Gitlab::VERSION,
timestamp: timestamp.iso8601,
date: timestamp.to_date.to_s,
license_key: key,
......
---
title: Sync gitlab version during seat link
merge_request: 60362
author:
type: changed
......@@ -81,6 +81,7 @@ RSpec.describe Gitlab::SeatLinkData do
it 'returns payload data as a JSON string' do
expect(subject.to_json).to eq(
{
gitlab_version: Gitlab::VERSION,
timestamp: timestamp.iso8601,
date: timestamp.to_date.iso8601,
license_key: key,
......
......@@ -18,6 +18,7 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
expect(WebMock).to have_requested(:post, seat_link_url).with(
headers: { 'Content-Type' => 'application/json' },
body: {
gitlab_version: Gitlab::VERSION,
timestamp: '2019-12-31T23:20:12Z',
date: '2019-12-31',
license_key: '123',
......@@ -71,6 +72,7 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
expect(WebMock).to have_requested(:post, seat_link_url).with(
headers: { 'Content-Type' => 'application/json' },
body: {
gitlab_version: Gitlab::VERSION,
timestamp: '2020-01-01T00:00:00Z',
date: '2020-01-01',
license_key: '123',
......
......@@ -14,6 +14,18 @@ module Gitlab
class_attributes[name] || superclass_attributes(name)
end
def set_class_attribute(name, value)
class_attributes[name] = value
after_hooks.each(&:call)
value
end
def after_set_class_attribute(&block)
after_hooks << block
end
private
def class_attributes
......@@ -25,6 +37,10 @@ module Gitlab
superclass.get_class_attribute(name)
end
def after_hooks
@after_hooks ||= []
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqConfig
class WorkerRouter
InvalidRoutingRuleError = Class.new(StandardError)
RuleEvaluator = Struct.new(:matcher, :queue_name)
def self.queue_name_from_worker_name(worker_klass)
base_queue_name =
worker_klass.name
.delete_prefix('Gitlab::')
.delete_suffix('Worker')
.underscore
.tr('/', '_')
[worker_klass.queue_namespace, base_queue_name].compact.join(':')
end
def self.global
@global_worker_router ||= new(::Gitlab.config.sidekiq.routing_rules)
rescue InvalidRoutingRuleError, ::Gitlab::SidekiqConfig::WorkerMatcher::UnknownPredicate => e
::Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
@global_worker_router = new([])
end
# call-seq:
# router = WorkerRouter.new([
# ["resource_boundary=cpu", 'cpu_boundary'],
# ["feature_category=pages", nil],
# ["feature_category=source_code_management", ''],
# ["*", "default"]
# ])
# router.route(ACpuBoundaryWorker) # Return "cpu_boundary"
# router.route(JustAPagesWorker) # Return "just_a_pages_worker"
# router.route(PostReceive) # Return "post_receive"
# router.route(RandomWorker) # Return "default"
#
# This class is responsible for routing a Sidekiq worker to a certain
# queue defined in the input routing rules. The input routing rules, as
# described above, is an order-matter array of tuples [query, queue_name].
#
# - The query syntax is the same as the "queue selector" detailedly
# denoted in doc/administration/operations/extra_sidekiq_processes.md.
#
# - The queue_name must be a valid Sidekiq queue name. If the queue name
# is nil, or an empty string, the worker is routed to the queue generated
# by the name of the worker instead.
#
# Rules are evaluated from first to last, and as soon as we find a match
# for a given worker we stop processing for that worker (first match
# wins). If the worker doesn't match any rule, it falls back the queue
# name generated from the worker name
#
# For further information, please visit:
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1016
#
def initialize(routing_rules)
@rule_evaluators = parse_routing_rules(routing_rules)
end
def route(worker_klass)
# A medium representation to ensure the backward-compatibility of
# WorkerMatcher
worker_metadata = generate_worker_metadata(worker_klass)
@rule_evaluators.each do |evaluator|
if evaluator.matcher.match?(worker_metadata)
return evaluator.queue_name.presence || queue_name_from_worker_name(worker_klass)
end
end
queue_name_from_worker_name(worker_klass)
end
private
def parse_routing_rules(routing_rules)
raise InvalidRoutingRuleError, 'The set of routing rule must be an array' unless routing_rules.is_a?(Array)
routing_rules.map do |rule_tuple|
raise InvalidRoutingRuleError, "Routing rule `#{rule_tuple.inspect}` is invalid" unless valid_routing_rule?(rule_tuple)
selector, destination_queue = rule_tuple
RuleEvaluator.new(
::Gitlab::SidekiqConfig::WorkerMatcher.new(selector),
destination_queue
)
end
end
def valid_routing_rule?(rule_tuple)
rule_tuple.is_a?(Array) && rule_tuple.length == 2
end
def generate_worker_metadata(worker_klass)
# The ee indicator here is insignificant and irrelevant to the matcher.
# Plus, it's not easy to determine whether a worker is **only**
# available in EE.
::Gitlab::SidekiqConfig::Worker.new(worker_klass, ee: false).to_yaml
end
def queue_name_from_worker_name(worker_klass)
self.class.queue_name_from_worker_name(worker_klass)
end
end
end
end
......@@ -38135,6 +38135,9 @@ msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
msgid "mrWidget|Merging! Drum roll, please…"
msgstr ""
msgid "mrWidget|More information"
msgstr ""
......@@ -38228,9 +38231,6 @@ msgstr ""
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr ""
msgid "mrWidget|This merge request is in the process of being merged"
msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled"
msgstr ""
......
......@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Admin Groups' do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
......@@ -202,6 +203,7 @@ RSpec.describe 'Admin Groups' do
select2(Gitlab::Access::REPORTER, from: '#access_level')
end
click_button "Add users to group"
page.within ".group-users-list" do
expect(page).to have_content(user.name)
expect(page).to have_content('Reporter')
......@@ -220,19 +222,13 @@ RSpec.describe 'Admin Groups' do
describe 'add admin himself to a group' do
before do
stub_feature_flags(invite_members_group_modal: false)
group.add_user(:user, Gitlab::Access::OWNER)
end
it 'adds admin a to a group as developer', :js do
visit group_group_members_path(group)
page.within '.invite-users-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
select 'Developer', from: 'access_level'
end
click_button 'Invite'
invite_member(current_user.name, role: 'Developer')
page.within members_table do
expect(page).to have_content(current_user.name)
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe "Admin::Projects" do
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
include Select2Helper
let(:user) { create :user }
......@@ -95,21 +96,27 @@ RSpec.describe "Admin::Projects" do
describe 'admin adds themselves to the project', :js do
before do
project.add_maintainer(user)
stub_feature_flags(invite_members_group_modal: false)
end
it 'adds admin to the project as developer' do
visit project_project_members_path(project)
page.within '.invite-users-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
select 'Developer', from: 'access_level'
end
click_button 'Invite'
invite_member(current_user.name, role: 'Developer')
expect(find_member_row(current_user)).to have_content('Developer')
end
context 'with the invite_members_group_modal feature flag disabled' do
it 'adds admin to the project as developer' do
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project)
add_member_using_form(current_user.id, role: 'Developer')
expect(find_member_row(current_user)).to have_content('Developer')
end
end
end
describe 'admin removes themselves from the project', :js do
......@@ -134,4 +141,19 @@ RSpec.describe "Admin::Projects" do
expect(current_path).to match dashboard_projects_path
end
end
# temporary method for the form until the :invite_members_group_modal feature flag is
# enabled: https://gitlab.com/gitlab-org/gitlab/-/issues/247208
def add_member_using_form(id, role: 'Developer')
page.within '.invite-users-form' do
select2(id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: 5.days.from_now.to_date
find_field('expires_at').native.send_keys :enter
select(role, from: "access_level")
click_on 'Invite'
end
end
end
......@@ -266,6 +266,7 @@ RSpec.describe 'Admin updates settings' do
fill_in 'service[push_channel]', with: '#test_channel'
page.check('Notify only broken pipelines')
page.select 'All branches', from: 'Branches to be notified'
page.select 'Match any of the labels', from: 'Labels to be notified behavior'
check_all_events
click_button 'Save changes'
......
......@@ -5,13 +5,13 @@ require 'spec_helper'
RSpec.describe 'Groups > Members > Manage members' do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') }
let(:group) { create(:group) }
before do
stub_feature_flags(invite_members_group_modal: false)
sign_in(user1)
end
......@@ -27,15 +27,15 @@ RSpec.describe 'Groups > Members > Manage members' do
end
context 'when Invite Members modal is enabled' do
before do
stub_feature_flags(invite_members_group_modal: true)
end
it_behaves_like 'includes the correct Invite link', '.js-invite-members-trigger', '.invite-users-form'
it_behaves_like 'includes the correct Invite link', '.js-invite-group-trigger', '.invite-group-form'
end
context 'when Invite Members modal is disabled' do
before do
stub_feature_flags(invite_members_group_modal: false)
end
it_behaves_like 'includes the correct Invite link', '.invite-users-form', '.js-invite-members-trigger'
it_behaves_like 'includes the correct Invite link', '.invite-group-form', '.js-invite-group-trigger'
end
......@@ -59,7 +59,7 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
add_user(user2.id, 'Reporter')
invite_member(user2.name, role: 'Reporter')
page.within(second_row) do
expect(page).to have_content(user2.name)
......@@ -73,21 +73,46 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
find('.select2-container').click
select_input = find('.select2-input')
click_on 'Invite members'
fill_in 'Select members or type email addresses', with: '@gitlab.com'
select_input.send_keys('@gitlab.com')
wait_for_requests
expect(page).to have_content('No matches found')
select_input.native.clear
select_input.send_keys('undisclosed_email@gitlab.com')
fill_in 'Select members or type email addresses', with: 'undisclosed_email@gitlab.com'
wait_for_requests
expect(page).to have_content("Jane 'invisible' Doe")
end
context 'when Invite Members modal is disabled' do
before do
stub_feature_flags(invite_members_group_modal: false)
end
it 'do not disclose email addresses', :js do
group.add_owner(user1)
create(:user, email: 'undisclosed_email@gitlab.com', name: "Jane 'invisible' Doe")
visit group_group_members_path(group)
find('.select2-container').click
select_input = find('.select2-input')
select_input.send_keys('@gitlab.com')
wait_for_requests
expect(page).to have_content('No matches found')
select_input.native.clear
select_input.send_keys('undisclosed_email@gitlab.com')
wait_for_requests
expect(page).to have_content("Jane 'invisible' Doe")
end
end
it 'remove user from group', :js do
group.add_owner(user1)
group.add_developer(user2)
......@@ -115,7 +140,7 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
add_user(user1.id, 'Reporter')
invite_member(user1.name, role: 'Reporter')
page.within(first_row) do
expect(page).to have_content(user1.name)
......@@ -128,7 +153,7 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
add_user('test@example.com', 'Reporter')
invite_member('test@example.com', role: 'Reporter')
expect(page).to have_link 'Invited'
click_link 'Invited'
......@@ -148,6 +173,8 @@ RSpec.describe 'Groups > Members > Manage members' do
expect(page).not_to have_selector '.invite-users-form'
expect(page).not_to have_selector '.invite-group-form'
expect(page).not_to have_selector '.js-invite-members-modal'
expect(page).not_to have_selector '.js-invite-group-modal'
page.within(second_row) do
# Can not modify user2 role
......@@ -157,12 +184,4 @@ RSpec.describe 'Groups > Members > Manage members' do
expect(page).not_to have_selector 'button[title="Remove member"]'
end
end
def add_user(id, role)
page.within ".invite-users-form" do
select2(id, from: "#user_ids", multiple: true)
select(role, from: "access_level")
click_button "Invite"
end
end
end
......@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let_it_be(:user1) { create(:user, name: 'John Doe') }
let_it_be(:group) { create(:group) }
......@@ -12,7 +12,6 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
let(:new_member) { create(:user, name: 'Mary Jane') }
before do
stub_feature_flags(invite_members_group_modal: false)
group.add_owner(user1)
sign_in(user1)
end
......@@ -20,14 +19,7 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
it 'expiration date is displayed in the members list' do
visit group_group_members_path(group)
page.within invite_users_form do
select2(new_member.id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: 5.days.from_now.to_date
find_field('expires_at').native.send_keys :enter
click_on 'Invite'
end
invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date)
page.within second_row do
expect(page).to have_content(/in \d days/)
......
......@@ -445,7 +445,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
wait_for_requests
expect(page).not_to have_button('Merge')
expect(page).to have_content('This merge request is in the process of being merged')
expect(page).to have_content('Merging! Drum roll, please…')
end
end
......
......@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe 'Project members list', :js do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') }
......@@ -12,8 +12,6 @@ RSpec.describe 'Project members list', :js do
let(:project) { create(:project, :internal, namespace: group) }
before do
stub_feature_flags(invite_members_group_modal: true)
sign_in(user1)
group.add_owner(user1)
end
......@@ -52,7 +50,7 @@ RSpec.describe 'Project members list', :js do
it 'add user to project' do
visit_members_page
add_user(user2.name, 'Reporter')
invite_member(user2.name, role: 'Reporter')
page.within find_member_row(user2) do
expect(page).to have_button('Reporter')
......@@ -100,7 +98,7 @@ RSpec.describe 'Project members list', :js do
it 'invite user to project' do
visit_members_page
add_user('test@example.com', 'Reporter')
invite_member('test@example.com', role: 'Reporter')
click_link 'Invited'
......@@ -171,25 +169,6 @@ RSpec.describe 'Project members list', :js do
private
def add_user(id, role)
click_on 'Invite members'
page.within '#invite-members-modal' do
fill_in 'Select members or type email addresses', with: id
wait_for_requests
click_button id
click_button 'Guest'
wait_for_requests
click_button role
click_button 'Invite'
end
page.refresh
end
def visit_members_page
visit project_project_members_path(project)
end
......
......@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe 'Projects > Members > Maintainer adds member with expiration date', :js do
include Select2Helper
include ActiveSupport::Testing::TimeHelpers
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project) }
......@@ -20,18 +20,9 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
end
it 'expiration date is displayed in the members list' do
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project)
page.within '.invite-users-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: 5.days.from_now.to_date
find_field('expires_at').native.send_keys :enter
click_on 'Invite'
end
invite_member(new_member.name, role: 'Guest', expires_at: 5.days.from_now.to_date)
page.within find_member_row(new_member) do
expect(page).to have_content(/in \d days/)
......
......@@ -4,6 +4,7 @@ import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_wid
describe('MRWidgetMerging', () => {
let wrapper;
const GlEmoji = { template: '<img />' };
beforeEach(() => {
wrapper = shallowMount(MrWidgetMerging, {
propsData: {
......@@ -12,6 +13,9 @@ describe('MRWidgetMerging', () => {
targetBranch: 'branch',
},
},
stubs: {
GlEmoji,
},
});
});
......@@ -27,7 +31,7 @@ describe('MRWidgetMerging', () => {
.trim()
.replace(/\s\s+/g, ' ')
.replace(/[\r\n]+/g, ' '),
).toContain('This merge request is in the process of being merged');
).toContain('Merging! Drum roll, please…');
});
it('renders branch information', () => {
......
......@@ -150,8 +150,6 @@ RSpec.describe InviteMembersHelper do
end
it 'returns false' do
stub_feature_flags(invite_members_group_modal: false)
expect(helper.can_invite_members_for_group?(group)).to eq false
expect(helper).not_to have_received(:can?)
end
......
......@@ -6,36 +6,62 @@ RSpec.describe Gitlab::ClassAttributes do
Class.new do
include Gitlab::ClassAttributes
def self.get_attribute(name)
get_class_attribute(name)
class << self
attr_reader :counter_1, :counter_2
# get_class_attribute and set_class_attribute are protected,
# hence those methods are for testing purpose
def get_attribute(name)
get_class_attribute(name)
end
def set_attribute(name, value)
set_class_attribute(name, value)
end
end
after_set_class_attribute do
@counter_1 ||= 0
@counter_1 += 1
end
def self.set_attribute(name, value)
class_attributes[name] = value
after_set_class_attribute do
@counter_2 ||= 0
@counter_2 += 2
end
end
end
let(:subclass) { Class.new(klass) }
describe ".get_class_attribute" do
it "returns values set on the class" do
klass.set_attribute(:foo, :bar)
it "returns values set on the class" do
klass.set_attribute(:foo, :bar)
expect(klass.get_attribute(:foo)).to eq(:bar)
end
expect(klass.get_attribute(:foo)).to eq(:bar)
end
it "returns values set on a superclass" do
klass.set_attribute(:foo, :bar)
it "returns values set on a superclass" do
klass.set_attribute(:foo, :bar)
expect(subclass.get_attribute(:foo)).to eq(:bar)
end
expect(subclass.get_attribute(:foo)).to eq(:bar)
end
it "returns values from the subclass over attributes from a superclass" do
klass.set_attribute(:foo, :baz)
subclass.set_attribute(:foo, :bar)
it "returns values from the subclass over attributes from a superclass" do
klass.set_attribute(:foo, :baz)
subclass.set_attribute(:foo, :bar)
expect(subclass.get_attribute(:foo)).to eq(:bar)
end
expect(klass.get_attribute(:foo)).to eq(:baz)
expect(subclass.get_attribute(:foo)).to eq(:bar)
end
it "triggers after hooks after set class values" do
expect(klass.counter_1).to be(nil)
expect(klass.counter_2).to be(nil)
klass.set_attribute(:foo, :bar)
klass.set_attribute(:foo, :bar)
expect(klass.counter_1).to eq(2)
expect(klass.counter_2).to eq(4)
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'rspec-parameterized'
RSpec.describe Gitlab::SidekiqConfig::WorkerRouter do
describe '.queue_name_from_worker_name' do
using RSpec::Parameterized::TableSyntax
def create_worker(name, namespace = nil)
Class.new.tap do |worker|
worker.define_singleton_method(:name) { name }
worker.define_singleton_method(:queue_namespace) { namespace }
end
end
where(:worker, :expected_name) do
create_worker('PagesWorker') | 'pages'
create_worker('PipelineNotificationWorker') | 'pipeline_notification'
create_worker('PostReceive') | 'post_receive'
create_worker('PostReceive', :git) | 'git:post_receive'
create_worker('PipelineHooksWorker', :pipeline_hooks) | 'pipeline_hooks:pipeline_hooks'
create_worker('Gitlab::JiraImport::AdvanceStageWorker') | 'jira_import_advance_stage'
create_worker('Gitlab::PhabricatorImport::ImportTasksWorker', :importer) | 'importer:phabricator_import_import_tasks'
end
with_them do
it 'generates a valid queue name from worker name' do
expect(described_class.queue_name_from_worker_name(worker)).to eql(expected_name)
end
end
end
shared_context 'router examples setup' do
using RSpec::Parameterized::TableSyntax
let(:worker) do
Class.new do
def self.name
'Gitlab::Foo::BarWorker'
end
include ApplicationWorker
feature_category :feature_a
urgency :low
worker_resource_boundary :cpu
tags :expensive
end
end
where(:routing_rules, :expected_queue) do
# Default, no configuration
[] | 'foo_bar'
# Does not match, fallback to the named queue
[
['feature_category=feature_b|urgency=high', 'queue_a'],
['resource_boundary=memory', 'queue_b'],
['tags=cheap', 'queue_c']
] | 'foo_bar'
# Match a nil queue, fallback to named queue
[
['feature_category=feature_b|urgency=high', 'queue_a'],
['resource_boundary=cpu', nil],
['tags=cheap', 'queue_c']
] | 'foo_bar'
# Match an empty string, fallback to named queue
[
['feature_category=feature_b|urgency=high', 'queue_a'],
['resource_boundary=cpu', ''],
['tags=cheap', 'queue_c']
] | 'foo_bar'
# Match the first rule
[
['feature_category=feature_a|urgency=high', 'queue_a'],
['resource_boundary=cpu', 'queue_b'],
['tags=cheap', 'queue_c']
] | 'queue_a'
# Match the first rule 2
[
['feature_category=feature_b|urgency=low', 'queue_a'],
['resource_boundary=cpu', 'queue_b'],
['tags=cheap', 'queue_c']
] | 'queue_a'
# Match the third rule
[
['feature_category=feature_b|urgency=high', 'queue_a'],
['resource_boundary=memory', 'queue_b'],
['tags=expensive', 'queue_c']
] | 'queue_c'
# Match all, first match wins
[
['feature_category=feature_a|urgency=low', 'queue_a'],
['resource_boundary=cpu', 'queue_b'],
['tags=expensive', 'queue_c']
] | 'queue_a'
# Match the same rule multiple times, the first match wins
[
['feature_category=feature_a', 'queue_a'],
['feature_category=feature_a', 'queue_b'],
['feature_category=feature_a', 'queue_c']
] | 'queue_a'
# Match wildcard
[
['feature_category=feature_b|urgency=high', 'queue_a'],
['resource_boundary=memory', 'queue_b'],
['tags=cheap', 'queue_c'],
['*', 'default']
] | 'default'
# Match wildcard at the top of the chain. It makes the following rules useless
[
['*', 'queue_foo'],
['feature_category=feature_a|urgency=low', 'queue_a'],
['resource_boundary=cpu', 'queue_b'],
['tags=expensive', 'queue_c']
] | 'queue_foo'
end
end
describe '.global' do
before do
described_class.remove_instance_variable(:@global_worker_router) if described_class.instance_variable_defined?(:@global_worker_router)
end
after do
described_class.remove_instance_variable(:@global_worker_router)
end
context 'valid routing rules' do
include_context 'router examples setup'
with_them do
before do
stub_config(sidekiq: { routing_rules: routing_rules })
end
it 'routes the worker to the correct queue' do
expect(described_class.global.route(worker)).to eql(expected_queue)
end
end
end
context 'invalid routing rules' do
let(:worker) do
Class.new do
def self.name
'Gitlab::Foo::BarWorker'
end
include ApplicationWorker
end
end
before do
stub_config(sidekiq: { routing_rules: routing_rules })
end
context 'invalid routing rules format' do
let(:routing_rules) { ['feature_category=a'] }
it 'captures the error and falls back to an empty route' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(be_a(described_class::InvalidRoutingRuleError))
expect(described_class.global.route(worker)).to eql('foo_bar')
end
end
context 'invalid predicate' do
let(:routing_rules) { [['invalid_term=a', 'queue_a']] }
it 'captures the error and falls back to an empty route' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(
be_a(Gitlab::SidekiqConfig::WorkerMatcher::UnknownPredicate)
)
expect(described_class.global.route(worker)).to eql('foo_bar')
end
end
end
end
describe '#route' do
context 'valid routing rules' do
include_context 'router examples setup'
with_them do
it 'routes the worker to the correct queue' do
router = described_class.new(routing_rules)
expect(router.route(worker)).to eql(expected_queue)
end
end
end
context 'invalid routing rules' do
it 'raises an exception' do
expect { described_class.new(nil) }.to raise_error(described_class::InvalidRoutingRuleError)
expect { described_class.new(['feature_category=a']) }.to raise_error(described_class::InvalidRoutingRuleError)
expect { described_class.new([['feature_category=a', 'queue_a', 'queue_b']]) }.to raise_error(described_class::InvalidRoutingRuleError)
expect do
described_class.new(
[
['feature_category=a', 'queue_b'],
['feature_category=b']
]
)
end.to raise_error(described_class::InvalidRoutingRuleError)
expect { described_class.new([['invalid_term=a', 'queue_a']]) }.to raise_error(Gitlab::SidekiqConfig::WorkerMatcher::UnknownPredicate)
end
end
end
end
......@@ -11,6 +11,10 @@ RSpec.describe ChatNotificationService do
it { is_expected.to validate_presence_of :webhook }
end
describe 'validations' do
it { is_expected.to validate_inclusion_of(:labels_to_be_notified_behavior).in_array(%w[match_any match_all]) }
end
describe '#can_test?' do
context 'with empty repository' do
it 'returns true' do
......@@ -32,8 +36,9 @@ RSpec.describe ChatNotificationService do
describe '#execute' do
subject(:chat_service) { described_class.new }
let_it_be(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:webhook_url) { 'https://example.gitlab.com/' }
let(:data) { Gitlab::DataBuilder::Push.build_sample(subject.project, user) }
......@@ -76,9 +81,12 @@ RSpec.describe ChatNotificationService do
end
context 'when the data object has a label' do
let(:label) { create(:label, project: project, name: 'Bug')}
let(:issue) { create(:labeled_issue, project: project, labels: [label]) }
let(:note) { create(:note, noteable: issue, project: project)}
let_it_be(:label) { create(:label, name: 'Bug') }
let_it_be(:label_2) { create(:label, name: 'Community contribution') }
let_it_be(:label_3) { create(:label, name: 'Backend') }
let_it_be(:issue) { create(:labeled_issue, project: project, labels: [label, label_2, label_3]) }
let_it_be(:note) { create(:note, noteable: issue, project: project) }
let(:data) { Gitlab::DataBuilder::Note.build(note, user) }
it 'notifies the chat service' do
......@@ -87,23 +95,123 @@ RSpec.describe ChatNotificationService do
chat_service.execute(data)
end
context 'and the chat_service has a label filter that does not matches the label' do
subject(:chat_service) { described_class.new(labels_to_be_notified: '~some random label') }
shared_examples 'notifies the chat service' do
specify do
expect(chat_service).to receive(:notify).with(any_args)
chat_service.execute(data)
end
end
it 'does not notify the chat service' do
expect(chat_service).not_to receive(:notify)
shared_examples 'does not notify the chat service' do
specify do
expect(chat_service).not_to receive(:notify).with(any_args)
chat_service.execute(data)
end
end
context 'and the chat_service has a label filter that matches the label' do
subject(:chat_service) { described_class.new(labels_to_be_notified: '~Backend, ~Bug') }
context 'when labels_to_be_notified_behavior is not defined' do
subject(:chat_service) { described_class.new(labels_to_be_notified: label_filter) }
it 'notifies the chat service' do
expect(chat_service).to receive(:notify).with(any_args)
context 'no matching labels' do
let(:label_filter) { '~some random label' }
chat_service.execute(data)
it_behaves_like 'does not notify the chat service'
end
context 'only one label matches' do
let(:label_filter) { '~some random label, ~Bug' }
it_behaves_like 'notifies the chat service'
end
end
context 'when labels_to_be_notified_behavior is match_any' do
subject(:chat_service) do
described_class.new(
labels_to_be_notified: label_filter,
labels_to_be_notified_behavior: 'match_any'
)
end
context 'no label filter' do
let(:label_filter) { nil }
it_behaves_like 'notifies the chat service'
end
context 'no matching labels' do
let(:label_filter) { '~some random label' }
it_behaves_like 'does not notify the chat service'
end
context 'only one label matches' do
let(:label_filter) { '~some random label, ~Bug' }
it_behaves_like 'notifies the chat service'
end
end
context 'when labels_to_be_notified_behavior is match_all' do
subject(:chat_service) do
described_class.new(
labels_to_be_notified: label_filter,
labels_to_be_notified_behavior: 'match_all'
)
end
context 'no label filter' do
let(:label_filter) { nil }
it_behaves_like 'notifies the chat service'
end
context 'no matching labels' do
let(:label_filter) { '~some random label' }
it_behaves_like 'does not notify the chat service'
end
context 'only one label matches' do
let(:label_filter) { '~some random label, ~Bug' }
it_behaves_like 'does not notify the chat service'
end
context 'labels matches exactly' do
let(:label_filter) { '~Bug, ~Backend, ~Community contribution' }
it_behaves_like 'notifies the chat service'
end
context 'labels matches but object has more' do
let(:label_filter) { '~Bug, ~Backend' }
it_behaves_like 'notifies the chat service'
end
context 'labels are distributed on multiple objects' do
let(:label_filter) { '~Bug, ~Backend' }
let(:data) do
Gitlab::DataBuilder::Note.build(note, user).merge({
issue: {
labels: [
{ title: 'Bug' }
]
},
merge_request: {
labels: [
{
title: 'Backend'
}
]
}
})
end
it_behaves_like 'does not notify the chat service'
end
end
end
......
# frozen_string_literal: true
module Spec
module Support
module Helpers
module Features
module InviteMembersModalHelper
def invite_member(name, role: 'Guest', expires_at: nil)
click_on 'Invite members'
page.within '#invite-members-modal' do
fill_in 'Select members or type email addresses', with: name
wait_for_requests
click_button name
fill_in 'YYYY-MM-DD', with: expires_at.try(:strftime, '%Y-%m-%d')
unless role == 'Guest'
click_button 'Guest'
wait_for_requests
click_button role
end
click_button 'Invite'
end
page.refresh
end
end
end
end
end
end
......@@ -27,10 +27,6 @@ module Spec
all_rows[2]
end
def invite_users_form
page.find('[data-testid="invite-users-form"]')
end
def find_row(name)
page.within(members_table) do
page.find('tbody > tr', text: name)
......
......@@ -30,6 +30,8 @@ Service.available_services_names.each do |service|
hash.merge!(k => '1,2,3')
elsif service == 'emails_on_push' && k == :recipients
hash.merge!(k => 'foo@bar.com')
elsif service == 'slack' || service == 'mattermost' && k == :labels_to_be_notified_behavior
hash.merge!(k => "match_any")
else
hash.merge!(k => "someword")
end
......
......@@ -3,7 +3,14 @@
require 'spec_helper'
RSpec.describe ApplicationWorker do
let_it_be(:worker) do
# We depend on the lazy-load characteristic of rspec. If the worker is loaded
# before setting up, it's likely to go wrong. Consider this catcha:
# before do
# allow(router).to receive(:route).with(worker).and_return('queue_1')
# end
# As worker is triggered, it includes ApplicationWorker, and the router is
# called before it is stubbed. That makes the stubbing useless.
let(:worker) do
Class.new do
def self.name
'Gitlab::Foo::Bar::DummyWorker'
......@@ -14,10 +21,77 @@ RSpec.describe ApplicationWorker do
end
let(:instance) { worker.new }
let(:router) { double(:router) }
describe 'Sidekiq options' do
it 'sets the queue name based on the class name' do
before do
allow(::Gitlab::SidekiqConfig::WorkerRouter).to receive(:global).and_return(router)
allow(router).to receive(:route).and_return('foo_bar_dummy')
end
describe 'Sidekiq attributes' do
it 'sets the queue name based on the output of the router' do
expect(worker.sidekiq_options['queue']).to eq('foo_bar_dummy')
expect(router).to have_received(:route).with(worker).at_least(:once)
end
context 'when a worker attribute is updated' do
before do
counter = 0
allow(router).to receive(:route) do
counter += 1
"queue_#{counter}"
end
end
it 'updates the queue name afterward' do
expect(worker.sidekiq_options['queue']).to eq('queue_1')
worker.feature_category :pages
expect(worker.sidekiq_options['queue']).to eq('queue_2')
worker.feature_category_not_owned!
expect(worker.sidekiq_options['queue']).to eq('queue_3')
worker.urgency :high
expect(worker.sidekiq_options['queue']).to eq('queue_4')
worker.worker_has_external_dependencies!
expect(worker.sidekiq_options['queue']).to eq('queue_5')
worker.worker_resource_boundary :cpu
expect(worker.sidekiq_options['queue']).to eq('queue_6')
worker.idempotent!
expect(worker.sidekiq_options['queue']).to eq('queue_7')
worker.weight 3
expect(worker.sidekiq_options['queue']).to eq('queue_8')
worker.tags :hello
expect(worker.sidekiq_options['queue']).to eq('queue_9')
worker.big_payload!
expect(worker.sidekiq_options['queue']).to eq('queue_10')
expect(router).to have_received(:route).with(worker).at_least(10).times
end
end
context 'when the worker is inherited' do
let(:sub_worker) { Class.new(worker) }
before do
allow(router).to receive(:route).and_return('queue_1')
worker # Force loading worker 1 to update its queue
allow(router).to receive(:route).and_return('queue_2')
end
it 'sets the queue name for the inherited worker' do
expect(sub_worker.sidekiq_options['queue']).to eq('queue_2')
expect(router).to have_received(:route).with(sub_worker).at_least(:once)
end
end
end
......@@ -74,11 +148,24 @@ RSpec.describe ApplicationWorker do
end
describe '.queue_namespace' do
it 'sets the queue name based on the class name' do
before do
allow(router).to receive(:route).and_return('foo_bar_dummy', 'some_namespace:foo_bar_dummy')
end
it 'updates the queue name from the router again' do
expect(worker.queue).to eq('foo_bar_dummy')
worker.queue_namespace :some_namespace
expect(worker.queue).to eq('some_namespace:foo_bar_dummy')
end
it 'updates the queue_namespace options of the worker' do
worker.queue_namespace :some_namespace
expect(worker.queue_namespace).to eql('some_namespace')
expect(worker.sidekiq_options['queue_namespace']).to be(:some_namespace)
end
end
describe '.queue' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment