Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8b75948934
commit
c9bdf91993
38 changed files with 323 additions and 447 deletions
|
@ -200,6 +200,13 @@ export default {
|
||||||
<gl-avatar :size="24" :src="snippet.author.avatarUrl" />
|
<gl-avatar :size="24" :src="snippet.author.avatarUrl" />
|
||||||
<span class="bold">{{ snippet.author.name }}</span>
|
<span class="bold">{{ snippet.author.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<gl-emoji
|
||||||
|
v-if="snippet.author.status"
|
||||||
|
v-gl-tooltip
|
||||||
|
class="gl-vertical-align-baseline font-size-inherit gl-mr-1"
|
||||||
|
:title="snippet.author.status.message"
|
||||||
|
:data-name="snippet.author.status.emoji"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</gl-sprintf>
|
</gl-sprintf>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,6 +59,11 @@ query GetSnippetQuery($ids: [SnippetID!]) {
|
||||||
name
|
name
|
||||||
username
|
username
|
||||||
webUrl
|
webUrl
|
||||||
|
status {
|
||||||
|
__typename
|
||||||
|
emoji
|
||||||
|
message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ module ApplicationHelper
|
||||||
# current_controller?('gitlab/application') # => false
|
# current_controller?('gitlab/application') # => false
|
||||||
def current_controller?(*args)
|
def current_controller?(*args)
|
||||||
args.any? do |v|
|
args.any? do |v|
|
||||||
v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path
|
Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_name || Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ module ApplicationHelper
|
||||||
# current_action?(:create) # => false
|
# current_action?(:create) # => false
|
||||||
# current_action?(:new, :create) # => true
|
# current_action?(:new, :create) # => true
|
||||||
def current_action?(*args)
|
def current_action?(*args)
|
||||||
args.any? { |v| v.to_s.downcase == action_name }
|
args.any? { |v| Gitlab::Utils.safe_downcase!(v.to_s) == action_name }
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_section?
|
def admin_section?
|
||||||
|
|
|
@ -72,7 +72,8 @@ module TabHelper
|
||||||
# Add our custom class into the html_options, which may or may not exist
|
# Add our custom class into the html_options, which may or may not exist
|
||||||
# and which may or may not already have a :class key
|
# and which may or may not already have a :class key
|
||||||
o = options.delete(:html_options) || {}
|
o = options.delete(:html_options) || {}
|
||||||
o[:class] = [*o[:class], klass].join(' ').strip
|
o[:class] = [*o[:class], klass].join(' ')
|
||||||
|
o[:class].strip!
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
content_tag(:li, capture(&block), o)
|
content_tag(:li, capture(&block), o)
|
||||||
|
|
|
@ -147,7 +147,6 @@ class Project < ApplicationRecord
|
||||||
has_many :boards
|
has_many :boards
|
||||||
|
|
||||||
# Project services
|
# Project services
|
||||||
has_one :alerts_service
|
|
||||||
has_one :campfire_service
|
has_one :campfire_service
|
||||||
has_one :datadog_service
|
has_one :datadog_service
|
||||||
has_one :discord_service
|
has_one :discord_service
|
||||||
|
@ -1357,9 +1356,9 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def disabled_services
|
def disabled_services
|
||||||
return ['datadog'] unless Feature.enabled?(:datadog_ci_integration, self)
|
return %w(datadog alerts) unless Feature.enabled?(:datadog_ci_integration, self)
|
||||||
|
|
||||||
[]
|
%w(alerts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_initialize_service(name)
|
def find_or_initialize_service(name)
|
||||||
|
|
|
@ -1,54 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'securerandom'
|
# This service is scheduled for removal. All records must
|
||||||
|
# be deleted before the class can be removed.
|
||||||
|
# https://gitlab.com/groups/gitlab-org/-/epics/5056
|
||||||
class AlertsService < Service
|
class AlertsService < Service
|
||||||
has_one :data, class_name: 'AlertsServiceData', autosave: true,
|
before_save :prevent_save
|
||||||
inverse_of: :service, foreign_key: :service_id
|
|
||||||
|
|
||||||
attribute :token, :string
|
|
||||||
delegate :token, :token=, :token_changed?, :token_was, to: :data
|
|
||||||
|
|
||||||
validates :token, presence: true, if: :activated?
|
|
||||||
|
|
||||||
before_validation :prevent_token_assignment
|
|
||||||
before_validation :ensure_token, if: :activated?
|
|
||||||
|
|
||||||
after_save :update_http_integration
|
|
||||||
|
|
||||||
def url
|
|
||||||
return if instance? || template?
|
|
||||||
|
|
||||||
url_helpers.project_alerts_notify_url(project, format: :json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def json_fields
|
|
||||||
super + %w(token)
|
|
||||||
end
|
|
||||||
|
|
||||||
def editable?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_active_box?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_test?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def title
|
|
||||||
_('Alerts endpoint')
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
_('Authorize external services to send alerts to GitLab')
|
|
||||||
end
|
|
||||||
|
|
||||||
def detailed_description
|
|
||||||
description
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.to_param
|
def self.to_param
|
||||||
'alerts'
|
'alerts'
|
||||||
|
@ -58,33 +14,15 @@ class AlertsService < Service
|
||||||
%w()
|
%w()
|
||||||
end
|
end
|
||||||
|
|
||||||
def data
|
|
||||||
super || build_data
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def prevent_token_assignment
|
def prevent_save
|
||||||
self.token = token_was if token.present? && token_changed?
|
errors.add(:base, _('Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.'))
|
||||||
end
|
log_error('Prevented attempt to save or update deprecated AlertsService')
|
||||||
|
|
||||||
def ensure_token
|
# Stops execution of callbacks and database operation while
|
||||||
self.token = generate_token if token.blank?
|
# preserving expectations of #save (will not raise) & #save! (raises)
|
||||||
end
|
# https://guides.rubyonrails.org/active_record_callbacks.html#halting-execution
|
||||||
|
throw :abort # rubocop:disable Cop/BanCatchThrow
|
||||||
def generate_token
|
|
||||||
SecureRandom.hex
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_helpers
|
|
||||||
Gitlab::Routing.url_helpers
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_http_integration
|
|
||||||
return unless project_id && type == 'AlertsService'
|
|
||||||
|
|
||||||
AlertManagement::SyncAlertServiceDataService # rubocop: disable CodeReuse/ServiceClass
|
|
||||||
.new(self)
|
|
||||||
.execute
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'securerandom'
|
|
||||||
|
|
||||||
class AlertsServiceData < ApplicationRecord
|
|
||||||
belongs_to :service, class_name: 'AlertsService'
|
|
||||||
|
|
||||||
validates :service, presence: true
|
|
||||||
|
|
||||||
attr_encrypted :token,
|
|
||||||
mode: :per_attribute_iv,
|
|
||||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
|
||||||
algorithm: 'aes-256-gcm'
|
|
||||||
|
|
||||||
def token_changed?
|
|
||||||
attribute_changed?(:token)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -19,7 +19,6 @@ class Service < ApplicationRecord
|
||||||
|
|
||||||
PROJECT_SPECIFIC_SERVICE_NAMES = %w[
|
PROJECT_SPECIFIC_SERVICE_NAMES = %w[
|
||||||
jenkins
|
jenkins
|
||||||
alerts
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# Fake services to help with local development.
|
# Fake services to help with local development.
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AlertManagement
|
|
||||||
class SyncAlertServiceDataService
|
|
||||||
# @param alert_service [AlertsService]
|
|
||||||
def initialize(alert_service)
|
|
||||||
@alert_service = alert_service
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
http_integration = find_http_integration
|
|
||||||
|
|
||||||
result = if http_integration
|
|
||||||
update_integration_data(http_integration)
|
|
||||||
else
|
|
||||||
create_integration
|
|
||||||
end
|
|
||||||
|
|
||||||
result ? ServiceResponse.success : ServiceResponse.error(message: 'Update failed')
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :alert_service
|
|
||||||
|
|
||||||
def find_http_integration
|
|
||||||
AlertManagement::HttpIntegrationsFinder.new(
|
|
||||||
alert_service.project,
|
|
||||||
endpoint_identifier: ::AlertManagement::HttpIntegration::LEGACY_IDENTIFIER
|
|
||||||
)
|
|
||||||
.execute
|
|
||||||
.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_integration
|
|
||||||
new_integration = AlertManagement::HttpIntegration.create(
|
|
||||||
project_id: alert_service.project_id,
|
|
||||||
name: 'HTTP endpoint',
|
|
||||||
endpoint_identifier: AlertManagement::HttpIntegration::LEGACY_IDENTIFIER,
|
|
||||||
active: alert_service.active,
|
|
||||||
encrypted_token: alert_service.data.encrypted_token,
|
|
||||||
encrypted_token_iv: alert_service.data.encrypted_token_iv
|
|
||||||
)
|
|
||||||
|
|
||||||
new_integration.persisted?
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_integration_data(http_integration)
|
|
||||||
http_integration.update(
|
|
||||||
active: alert_service.active,
|
|
||||||
encrypted_token: alert_service.data.encrypted_token,
|
|
||||||
encrypted_token_iv: alert_service.data.encrypted_token_iv
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
.js-alerts-service-settings{ data: alerts_settings_data(disabled: true) }
|
|
|
@ -1,8 +0,0 @@
|
||||||
.row
|
|
||||||
.col-lg-12
|
|
||||||
.gl-alert.gl-alert-info{ role: 'alert' }
|
|
||||||
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
|
||||||
.gl-alert-body
|
|
||||||
= _('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated.')
|
|
||||||
.gl-alert-actions
|
|
||||||
= link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'btn gl-alert-action btn-info new-gl-button'
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Show status of snippet author in header
|
||||||
|
merge_request: 51030
|
||||||
|
author: Kev @KevSlashNull
|
||||||
|
type: fixed
|
5
changelogs/unreleased/lower-allocations-nav.yml
Normal file
5
changelogs/unreleased/lower-allocations-nav.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Lower allocations when building nav
|
||||||
|
merge_request: 51628
|
||||||
|
author:
|
||||||
|
type: performance
|
5
changelogs/unreleased/sy-remove-alerts-service.yml
Normal file
5
changelogs/unreleased/sy-remove-alerts-service.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove deprecated generic alert integration in favor of HTTP Integrations
|
||||||
|
merge_request: 50913
|
||||||
|
author:
|
||||||
|
type: removed
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RemoveAlertsServiceRecords < ActiveRecord::Migration[6.0]
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
class Service < ActiveRecord::Base
|
||||||
|
self.table_name = 'services'
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
Service.delete_by(type: 'AlertsService')
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# no-op
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20210107194543
Normal file
1
db/schema_migrations/20210107194543
Normal file
|
@ -0,0 +1 @@
|
||||||
|
d72cf1c88a060ccadd9f90cbef5ae7d4ea6a4416a6263d11a870e01b02d1f935
|
|
@ -22981,7 +22981,6 @@ type ServiceEdge {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServiceType {
|
enum ServiceType {
|
||||||
ALERTS_SERVICE
|
|
||||||
ASANA_SERVICE
|
ASANA_SERVICE
|
||||||
ASSEMBLA_SERVICE
|
ASSEMBLA_SERVICE
|
||||||
BAMBOO_SERVICE
|
BAMBOO_SERVICE
|
||||||
|
|
|
@ -66648,12 +66648,6 @@
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
"interfaces": null,
|
"interfaces": null,
|
||||||
"enumValues": [
|
"enumValues": [
|
||||||
{
|
|
||||||
"name": "ALERTS_SERVICE",
|
|
||||||
"description": null,
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "ASANA_SERVICE",
|
"name": "ASANA_SERVICE",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
|
@ -4921,7 +4921,6 @@ State of a Sentry error.
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| `ALERTS_SERVICE` | |
|
|
||||||
| `ASANA_SERVICE` | |
|
| `ASANA_SERVICE` | |
|
||||||
| `ASSEMBLA_SERVICE` | |
|
| `ASSEMBLA_SERVICE` | |
|
||||||
| `BAMBOO_SERVICE` | |
|
| `BAMBOO_SERVICE` | |
|
||||||
|
|
|
@ -272,7 +272,13 @@ The GitLab user only needs access when adding a new namespace. For syncing with
|
||||||
|
|
||||||
![Configure namespace on GitLab Jira App](img/jira_dev_panel_setup_com_3.png)
|
![Configure namespace on GitLab Jira App](img/jira_dev_panel_setup_com_3.png)
|
||||||
|
|
||||||
After a namespace is added, all future commits, branches, and merge requests of all projects under that namespace are synced to Jira. Past Merge Request data is initially synced. Past branch and commit data cannot be synced at the moment.
|
After a namespace is added:
|
||||||
|
|
||||||
|
- All future commits, branches, and merge requests of all projects under that namespace
|
||||||
|
are synced to Jira.
|
||||||
|
- From GitLab 13.8, past merge request data is synced to Jira.
|
||||||
|
|
||||||
|
Support for syncing past branch and commit data [is planned](https://gitlab.com/gitlab-org/gitlab/-/issues/263240).
|
||||||
|
|
||||||
For more information, see [Usage](#usage).
|
For more information, see [Usage](#usage).
|
||||||
|
|
||||||
|
|
|
@ -214,7 +214,7 @@ variables:
|
||||||
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
|
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
|
||||||
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process
|
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process
|
||||||
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process
|
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process
|
||||||
DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
|
DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
|
||||||
DAST_AUTH_VALIDATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
|
DAST_AUTH_VALIDATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -570,7 +570,7 @@ DAST can be [configured](#customizing-the-dast-settings) using environment varia
|
||||||
| `DAST_PASSWORD_FIELD` | string | The name of password field at the sign-in HTML form. |
|
| `DAST_PASSWORD_FIELD` | string | The name of password field at the sign-in HTML form. |
|
||||||
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
||||||
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
||||||
| `DAST_AUTH_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
| `DAST_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. In [GitLab 13.7 and earlier](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/367), was `DAST_AUTH_EXCLUDE_URLS` (which we plan to support until GitLab 14.0). |
|
||||||
| `DAST_FULL_SCAN_ENABLED` | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
|
| `DAST_FULL_SCAN_ENABLED` | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
|
||||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
|
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
|
||||||
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
|
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
|
||||||
|
|
|
@ -161,7 +161,6 @@ module API
|
||||||
|
|
||||||
def self.services
|
def self.services
|
||||||
{
|
{
|
||||||
'alerts' => [],
|
|
||||||
'asana' => [
|
'asana' => [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -807,7 +806,6 @@ module API
|
||||||
|
|
||||||
def self.service_classes
|
def self.service_classes
|
||||||
[
|
[
|
||||||
::AlertsService,
|
|
||||||
::AsanaService,
|
::AsanaService,
|
||||||
::AssemblaService,
|
::AssemblaService,
|
||||||
::BambooService,
|
::BambooService,
|
||||||
|
|
|
@ -24,7 +24,9 @@ module Gitlab
|
||||||
Gitlab::CurrentSettings.snowplow_enabled?
|
Gitlab::CurrentSettings.snowplow_enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
def event(category, action, label: nil, property: nil, value: nil, context: nil)
|
def event(category, action, label: nil, property: nil, value: nil, context: [], standard_context: nil)
|
||||||
|
context.push(standard_context.to_context) if standard_context
|
||||||
|
|
||||||
snowplow.event(category, action, label: label, property: property, value: value, context: context)
|
snowplow.event(category, action, label: label, property: property, value: value, context: context)
|
||||||
product_analytics.event(category, action, label: label, property: property, value: value, context: context)
|
product_analytics.event(category, action, label: label, property: property, value: value, context: context)
|
||||||
end
|
end
|
||||||
|
|
41
lib/gitlab/tracking/standard_context.rb
Normal file
41
lib/gitlab/tracking/standard_context.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Tracking
|
||||||
|
class StandardContext
|
||||||
|
GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-1'.freeze
|
||||||
|
|
||||||
|
def initialize(namespace: nil, project: nil, **data)
|
||||||
|
@namespace = namespace
|
||||||
|
@project = project
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
|
||||||
|
def namespace_id
|
||||||
|
namespace&.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def project_id
|
||||||
|
@project&.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_context
|
||||||
|
SnowplowTracker::SelfDescribingJson.new(GITLAB_STANDARD_SCHEMA_URL, to_h)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def namespace
|
||||||
|
@namespace || @project&.namespace
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_h
|
||||||
|
public_methods(false).each_with_object({}) do |method, hash|
|
||||||
|
next if method == :to_context
|
||||||
|
|
||||||
|
hash[method] = public_send(method) # rubocop:disable GitlabSecurity/PublicSend
|
||||||
|
end.merge(@data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -174,6 +174,18 @@ module Gitlab
|
||||||
rescue IPAddr::InvalidAddressError
|
rescue IPAddr::InvalidAddressError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A safe alternative to String#downcase!
|
||||||
|
#
|
||||||
|
# This will make copies of frozen strings but downcase unfrozen
|
||||||
|
# strings in place, reducing allocations.
|
||||||
|
def safe_downcase!(str)
|
||||||
|
if str.frozen?
|
||||||
|
str.downcase
|
||||||
|
else
|
||||||
|
str.downcase! || str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Converts a string to an Addressable::URI object.
|
# Converts a string to an Addressable::URI object.
|
||||||
# If the string is not a valid URI, it returns nil.
|
# If the string is not a valid URI, it returns nil.
|
||||||
# Param uri_string should be a String object.
|
# Param uri_string should be a String object.
|
||||||
|
|
|
@ -2760,7 +2760,7 @@ msgstr ""
|
||||||
msgid "Alerts"
|
msgid "Alerts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Alerts endpoint"
|
msgid "Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "AlertsIntegrations|Alerts will be created through this integration"
|
msgid "AlertsIntegrations|Alerts will be created through this integration"
|
||||||
|
@ -4151,9 +4151,6 @@ msgstr ""
|
||||||
msgid "Authorize %{user} to use your account?"
|
msgid "Authorize %{user} to use your account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Authorize external services to send alerts to GitLab"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Authorized %{new_chat_name}"
|
msgid "Authorized %{new_chat_name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32306,9 +32303,6 @@ msgstr ""
|
||||||
msgid "You can now export your security dashboard to a CSV report."
|
msgid "You can now export your security dashboard to a CSV report."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "You can now submit a merge request to get this change into the original branch."
|
msgid "You can now submit a merge request to get this change into the original branch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
|
||||||
factory :alerts_service_data do
|
|
||||||
service { association(:alerts_service) }
|
|
||||||
token { SecureRandom.hex }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -38,24 +38,6 @@ FactoryBot.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :alerts_service do
|
|
||||||
active
|
|
||||||
project
|
|
||||||
type { 'AlertsService' }
|
|
||||||
|
|
||||||
trait :active do
|
|
||||||
active { true }
|
|
||||||
end
|
|
||||||
|
|
||||||
trait :inactive do
|
|
||||||
active { false }
|
|
||||||
end
|
|
||||||
|
|
||||||
before(:create) do |service|
|
|
||||||
service.data = build(:alerts_service_data, service: service)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
factory :drone_ci_service do
|
factory :drone_ci_service do
|
||||||
project
|
project
|
||||||
active { true }
|
active { true }
|
||||||
|
|
|
@ -32,8 +32,8 @@ FactoryBot.define do
|
||||||
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
|
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
|
||||||
create(:project_error_tracking_setting, project: projects[0])
|
create(:project_error_tracking_setting, project: projects[0])
|
||||||
create(:project_error_tracking_setting, project: projects[1], enabled: false)
|
create(:project_error_tracking_setting, project: projects[1], enabled: false)
|
||||||
create(:alerts_service, project: projects[0])
|
create(:service, project: projects[0], type: 'AlertsService', active: true)
|
||||||
create(:alerts_service, :inactive, project: projects[1])
|
create(:service, project: projects[1], type: 'AlertsService', active: false)
|
||||||
alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot)
|
alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot)
|
||||||
create_list(:incident, 2, project: projects[1], author: User.alert_bot)
|
create_list(:incident, 2, project: projects[1], author: User.alert_bot)
|
||||||
issues = create_list(:issue, 4, project: projects[0])
|
issues = create_list(:issue, 4, project: projects[0])
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'User activates Alerts', :js do
|
|
||||||
let_it_be(:project) { create(:project) }
|
|
||||||
let_it_be(:user) { create(:user) }
|
|
||||||
|
|
||||||
let(:service_name) { 'alerts' }
|
|
||||||
let(:service_title) { 'Alerts endpoint' }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
project.add_maintainer(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when service is deactivated' do
|
|
||||||
it 'user cannot activate service' do
|
|
||||||
visit_project_services
|
|
||||||
|
|
||||||
expect(page).to have_link(service_title)
|
|
||||||
click_link(service_title)
|
|
||||||
|
|
||||||
expect(page).to have_callout_message
|
|
||||||
expect(page).to have_toggle_active_disabled
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when service is activated' do
|
|
||||||
let_it_be(:activated_alerts_service) do
|
|
||||||
create(:alerts_service, :active, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
visit_alerts_service
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'user cannot change settings' do
|
|
||||||
expect(page).to have_callout_message
|
|
||||||
expect(page).to have_toggle_active_disabled
|
|
||||||
expect(page).to have_button_reset_key_disabled
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def visit_project_services
|
|
||||||
visit(project_settings_integrations_path(project))
|
|
||||||
end
|
|
||||||
|
|
||||||
def visit_alerts_service
|
|
||||||
visit(edit_project_service_path(project, service_name))
|
|
||||||
end
|
|
||||||
|
|
||||||
def have_callout_message
|
|
||||||
within('.gl-alert') do
|
|
||||||
have_content('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page.')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def have_toggle_active_disabled
|
|
||||||
have_selector('#activated .project-feature-toggle.is-disabled')
|
|
||||||
end
|
|
||||||
|
|
||||||
def have_button_reset_key_disabled
|
|
||||||
have_button('Reset key', disabled: true)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -17,6 +17,8 @@ describe('Snippet header component', () => {
|
||||||
let err;
|
let err;
|
||||||
const originalRelativeUrlRoot = gon.relative_url_root;
|
const originalRelativeUrlRoot = gon.relative_url_root;
|
||||||
|
|
||||||
|
const GlEmoji = { template: '<img/>' };
|
||||||
|
|
||||||
function createComponent({
|
function createComponent({
|
||||||
loading = false,
|
loading = false,
|
||||||
permissions = {},
|
permissions = {},
|
||||||
|
@ -47,10 +49,15 @@ describe('Snippet header component', () => {
|
||||||
},
|
},
|
||||||
stubs: {
|
stubs: {
|
||||||
ApolloMutation,
|
ApolloMutation,
|
||||||
|
GlEmoji,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findAuthorEmoji = () => wrapper.find(GlEmoji);
|
||||||
|
const findAuthoredMessage = () => wrapper.find('[data-testid="authored-message"]').text();
|
||||||
|
const buttonCount = () => wrapper.findAll(GlButton).length;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
gon.relative_url_root = '/foo/';
|
gon.relative_url_root = '/foo/';
|
||||||
snippet = {
|
snippet = {
|
||||||
|
@ -66,6 +73,7 @@ describe('Snippet header component', () => {
|
||||||
project: null,
|
project: null,
|
||||||
author: {
|
author: {
|
||||||
name: 'Thor Odinson',
|
name: 'Thor Odinson',
|
||||||
|
status: null,
|
||||||
},
|
},
|
||||||
blobs: [Blob],
|
blobs: [Blob],
|
||||||
createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(),
|
createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(),
|
||||||
|
@ -100,17 +108,36 @@ describe('Snippet header component', () => {
|
||||||
it('renders a message showing snippet creation date and author', () => {
|
it('renders a message showing snippet creation date and author', () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
const text = wrapper.find('[data-testid="authored-message"]').text();
|
const text = findAuthoredMessage();
|
||||||
expect(text).toContain('Authored 1 month ago by');
|
expect(text).toContain('Authored 1 month ago by');
|
||||||
expect(text).toContain('Thor Odinson');
|
expect(text).toContain('Thor Odinson');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('author status', () => {
|
||||||
|
it('is rendered when it is set', () => {
|
||||||
|
snippet.author.status = {
|
||||||
|
message: 'At work',
|
||||||
|
emoji: 'hammer',
|
||||||
|
};
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findAuthorEmoji().attributes('title')).toBe(snippet.author.status.message);
|
||||||
|
expect(findAuthorEmoji().attributes('data-name')).toBe(snippet.author.status.emoji);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is not rendered when the user has no status', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findAuthorEmoji().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('renders a message showing only snippet creation date if author is null', () => {
|
it('renders a message showing only snippet creation date if author is null', () => {
|
||||||
snippet.author = null;
|
snippet.author = null;
|
||||||
|
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
const text = wrapper.find('[data-testid="authored-message"]').text();
|
const text = findAuthoredMessage();
|
||||||
expect(text).toBe('Authored 1 month ago');
|
expect(text).toBe('Authored 1 month ago');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,7 +148,7 @@ describe('Snippet header component', () => {
|
||||||
updateSnippet: false,
|
updateSnippet: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.findAll(GlButton).length).toEqual(0);
|
expect(buttonCount()).toEqual(0);
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
permissions: {
|
permissions: {
|
||||||
|
@ -129,7 +156,7 @@ describe('Snippet header component', () => {
|
||||||
updateSnippet: false,
|
updateSnippet: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.findAll(GlButton).length).toEqual(1);
|
expect(buttonCount()).toEqual(1);
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
permissions: {
|
permissions: {
|
||||||
|
@ -137,7 +164,7 @@ describe('Snippet header component', () => {
|
||||||
updateSnippet: true,
|
updateSnippet: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.findAll(GlButton).length).toEqual(2);
|
expect(buttonCount()).toEqual(2);
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
permissions: {
|
permissions: {
|
||||||
|
@ -149,7 +176,7 @@ describe('Snippet header component', () => {
|
||||||
canCreateSnippet: true,
|
canCreateSnippet: true,
|
||||||
});
|
});
|
||||||
return wrapper.vm.$nextTick().then(() => {
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
expect(wrapper.findAll(GlButton).length).toEqual(3);
|
expect(buttonCount()).toEqual(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -525,7 +525,6 @@ project:
|
||||||
- designs
|
- designs
|
||||||
- project_aliases
|
- project_aliases
|
||||||
- external_pull_requests
|
- external_pull_requests
|
||||||
- alerts_service
|
|
||||||
- grafana_integration
|
- grafana_integration
|
||||||
- remove_source_branch_after_merge
|
- remove_source_branch_after_merge
|
||||||
- deleting_user
|
- deleting_user
|
||||||
|
|
55
spec/lib/gitlab/tracking/standard_context_spec.rb
Normal file
55
spec/lib/gitlab/tracking/standard_context_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Tracking::StandardContext do
|
||||||
|
let_it_be(:project) { create(:project) }
|
||||||
|
let_it_be(:namespace) { create(:namespace) }
|
||||||
|
|
||||||
|
let(:snowplow_context) { subject.to_context }
|
||||||
|
|
||||||
|
describe '#to_context' do
|
||||||
|
context 'with no arguments' do
|
||||||
|
it 'creates a Snowplow context with no data' do
|
||||||
|
snowplow_context.to_json[:data].each do |_, v|
|
||||||
|
expect(v).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with extra data' do
|
||||||
|
subject { described_class.new(foo: 'bar') }
|
||||||
|
|
||||||
|
it 'creates a Snowplow context with the given data' do
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with namespace' do
|
||||||
|
subject { described_class.new(namespace: namespace) }
|
||||||
|
|
||||||
|
it 'creates a Snowplow context using the given data' do
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id)
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with project' do
|
||||||
|
subject { described_class.new(project: project) }
|
||||||
|
|
||||||
|
it 'creates a Snowplow context using the given data' do
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(project.namespace.id)
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with project and namespace' do
|
||||||
|
subject { described_class.new(namespace: namespace, project: project) }
|
||||||
|
|
||||||
|
it 'creates a Snowplow context using the given data' do
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id)
|
||||||
|
expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -41,23 +41,44 @@ RSpec.describe Gitlab::Tracking do
|
||||||
allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
|
allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'delegates to snowplow destination' do
|
shared_examples 'delegates to destination' do |klass|
|
||||||
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
|
context 'with standard context' do
|
||||||
.to receive(:event)
|
it "delegates to #{klass} destination" do
|
||||||
.with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
|
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
|
||||||
|
expect(category).to eq('category')
|
||||||
|
expect(action).to eq('action')
|
||||||
|
expect(args[:label]).to eq('label')
|
||||||
|
expect(args[:property]).to eq('property')
|
||||||
|
expect(args[:value]).to eq(1.5)
|
||||||
|
expect(args[:context].length).to eq(1)
|
||||||
|
expect(args[:context].first.to_json[:schema]).to eq(Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL)
|
||||||
|
expect(args[:context].first.to_json[:data]).to include(foo: 'bar')
|
||||||
|
end
|
||||||
|
|
||||||
|
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
|
||||||
|
standard_context: Gitlab::Tracking::StandardContext.new(foo: 'bar'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without standard context' do
|
||||||
|
it "delegates to #{klass} destination" do
|
||||||
|
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
|
||||||
|
expect(category).to eq('category')
|
||||||
|
expect(action).to eq('action')
|
||||||
|
expect(args[:label]).to eq('label')
|
||||||
|
expect(args[:property]).to eq('property')
|
||||||
|
expect(args[:value]).to eq(1.5)
|
||||||
|
end
|
||||||
|
|
||||||
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
|
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'delegates to ProductAnalytics destination' do
|
|
||||||
expect_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics)
|
|
||||||
.to receive(:event)
|
|
||||||
.with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
|
|
||||||
|
|
||||||
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
|
||||||
|
include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
|
||||||
|
end
|
||||||
|
|
||||||
describe '.self_describing_event' do
|
describe '.self_describing_event' do
|
||||||
it 'delegates to snowplow destination' do
|
it 'delegates to snowplow destination' do
|
||||||
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
|
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
|
||||||
|
|
|
@ -392,6 +392,23 @@ RSpec.describe Gitlab::Utils do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".safe_downcase!" do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
where(:str, :result) do
|
||||||
|
"test".freeze | "test"
|
||||||
|
"Test".freeze | "test"
|
||||||
|
"test" | "test"
|
||||||
|
"Test" | "test"
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it "downcases the string" do
|
||||||
|
expect(described_class.safe_downcase!(str)).to eq(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.parse_url' do
|
describe '.parse_url' do
|
||||||
it 'returns Addressable::URI object' do
|
it 'returns Addressable::URI object' do
|
||||||
expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI)
|
expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI)
|
||||||
|
|
30
spec/migrations/remove_alerts_service_records_spec.rb
Normal file
30
spec/migrations/remove_alerts_service_records_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require Rails.root.join('db', 'post_migrate', '20210107194543_remove_alerts_service_records.rb')
|
||||||
|
|
||||||
|
RSpec.describe RemoveAlertsServiceRecords do
|
||||||
|
let(:services) { table(:services) }
|
||||||
|
let(:alerts_service_data) { table(:alerts_service_data) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
5.times do
|
||||||
|
service = services.create!(type: 'AlertsService')
|
||||||
|
alerts_service_data.create!(service_id: service.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
services.create!(type: 'SomeOtherType')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes services records of type AlertsService and corresponding data', :aggregate_failures do
|
||||||
|
expect(services.count).to eq(6)
|
||||||
|
expect(alerts_service_data.count).to eq(5)
|
||||||
|
|
||||||
|
migrate!
|
||||||
|
|
||||||
|
expect(services.count).to eq(1)
|
||||||
|
expect(services.first.type).to eq('SomeOtherType')
|
||||||
|
expect(services.where(type: 'AlertsService')).to be_empty
|
||||||
|
expect(alerts_service_data.all).to be_empty
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,108 +2,38 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
|
# AlertsService is stripped down to only required methods
|
||||||
|
# to avoid errors loading integration-related pages if
|
||||||
|
# records are present.
|
||||||
RSpec.describe AlertsService do
|
RSpec.describe AlertsService do
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
let(:service_params) { { project: project, active: active } }
|
subject(:service) { described_class.new(project: project) }
|
||||||
let(:active) { true }
|
|
||||||
let(:service) { described_class.new(service_params) }
|
|
||||||
|
|
||||||
shared_context 'when active' do
|
it { is_expected.to be_valid }
|
||||||
let(:active) { true }
|
|
||||||
|
describe '#to_param' do
|
||||||
|
subject { service.to_param }
|
||||||
|
|
||||||
|
it { is_expected.to eq('alerts') }
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_context 'when inactive' do
|
describe '#supported_events' do
|
||||||
let(:active) { false }
|
subject { service.supported_events }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_context 'when persisted' do
|
describe '#save' do
|
||||||
before do
|
it 'prevents records from being created or updated' do
|
||||||
service.save!
|
expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
|
||||||
service.reload
|
hash_including(message: 'Prevented attempt to save or update deprecated AlertsService')
|
||||||
end
|
)
|
||||||
end
|
|
||||||
|
|
||||||
describe '#url' do
|
expect(service.save).to be_falsey
|
||||||
include Gitlab::Routing
|
|
||||||
|
|
||||||
subject { service.url }
|
expect(service.errors.full_messages).to include(
|
||||||
|
'Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.'
|
||||||
it { is_expected.to eq(project_alerts_notify_url(project, format: :json)) }
|
)
|
||||||
end
|
|
||||||
|
|
||||||
describe '#json_fields' do
|
|
||||||
subject { service.json_fields }
|
|
||||||
|
|
||||||
it { is_expected.to eq(%w(active token)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#as_json' do
|
|
||||||
subject { service.as_json(only: service.json_fields) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('active' => true, 'token' => nil) }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#token' do
|
|
||||||
shared_context 'reset token' do
|
|
||||||
before do
|
|
||||||
service.token = ''
|
|
||||||
service.valid?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_context 'assign token' do |token|
|
|
||||||
before do
|
|
||||||
service.token = token
|
|
||||||
service.valid?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'valid token' do
|
|
||||||
it { is_expected.to match(/\A\h{32}\z/) }
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'no token' do
|
|
||||||
it { is_expected.to be_blank }
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { service.token }
|
|
||||||
|
|
||||||
context 'when active' do
|
|
||||||
include_context 'when active'
|
|
||||||
|
|
||||||
context 'when resetting' do
|
|
||||||
let!(:previous_token) { service.token }
|
|
||||||
|
|
||||||
include_context 'reset token'
|
|
||||||
|
|
||||||
it_behaves_like 'valid token'
|
|
||||||
|
|
||||||
it { is_expected.not_to eq(previous_token) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when assigning' do
|
|
||||||
include_context 'assign token', 'random token'
|
|
||||||
|
|
||||||
it_behaves_like 'valid token'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when inactive' do
|
|
||||||
include_context 'when inactive'
|
|
||||||
|
|
||||||
context 'when resetting' do
|
|
||||||
let!(:previous_token) { service.token }
|
|
||||||
|
|
||||||
include_context 'reset token'
|
|
||||||
|
|
||||||
it_behaves_like 'no token'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when persisted' do
|
|
||||||
include_context 'when persisted'
|
|
||||||
|
|
||||||
it_behaves_like 'valid token'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe AlertManagement::SyncAlertServiceDataService do
|
|
||||||
let_it_be(:alerts_service) do
|
|
||||||
AlertsService.skip_callback(:save, :after, :update_http_integration)
|
|
||||||
service = create(:alerts_service, :active)
|
|
||||||
AlertsService.set_callback(:save, :after, :update_http_integration)
|
|
||||||
|
|
||||||
service
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#execute' do
|
|
||||||
subject(:execute) { described_class.new(alerts_service).execute }
|
|
||||||
|
|
||||||
context 'without http integration' do
|
|
||||||
it 'creates the integration' do
|
|
||||||
expect { execute }
|
|
||||||
.to change { AlertManagement::HttpIntegration.count }.by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a success' do
|
|
||||||
expect(subject.success?).to eq(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'existing legacy http integration' do
|
|
||||||
let_it_be(:integration) { create(:alert_management_http_integration, :legacy, project: alerts_service.project) }
|
|
||||||
|
|
||||||
it 'updates the integration' do
|
|
||||||
expect { execute }
|
|
||||||
.to change { integration.reload.encrypted_token }.to(alerts_service.data.encrypted_token)
|
|
||||||
.and change { integration.encrypted_token_iv }.to(alerts_service.data.encrypted_token_iv)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a success' do
|
|
||||||
expect(subject.success?).to eq(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'existing other http integration' do
|
|
||||||
let_it_be(:integration) { create(:alert_management_http_integration, project: alerts_service.project) }
|
|
||||||
|
|
||||||
it 'creates the integration' do
|
|
||||||
expect { execute }
|
|
||||||
.to change { AlertManagement::HttpIntegration.count }.by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a success' do
|
|
||||||
expect(subject.success?).to eq(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue