Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-15 03:10:30 +00:00
parent 8b75948934
commit c9bdf91993
38 changed files with 323 additions and 447 deletions

View File

@ -200,6 +200,13 @@ export default {
<gl-avatar :size="24" :src="snippet.author.avatarUrl" />
<span class="bold">{{ snippet.author.name }}</span>
</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>
</gl-sprintf>
</div>

View File

@ -59,6 +59,11 @@ query GetSnippetQuery($ids: [SnippetID!]) {
name
username
webUrl
status {
__typename
emoji
message
}
}
}
}

View File

@ -44,7 +44,7 @@ module ApplicationHelper
# current_controller?('gitlab/application') # => false
def current_controller?(*args)
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
@ -59,7 +59,7 @@ module ApplicationHelper
# current_action?(:create) # => false
# current_action?(:new, :create) # => true
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
def admin_section?

View File

@ -72,7 +72,8 @@ module TabHelper
# 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
o = options.delete(:html_options) || {}
o[:class] = [*o[:class], klass].join(' ').strip
o[:class] = [*o[:class], klass].join(' ')
o[:class].strip!
if block_given?
content_tag(:li, capture(&block), o)

View File

@ -147,7 +147,6 @@ class Project < ApplicationRecord
has_many :boards
# Project services
has_one :alerts_service
has_one :campfire_service
has_one :datadog_service
has_one :discord_service
@ -1357,9 +1356,9 @@ class Project < ApplicationRecord
end
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
def find_or_initialize_service(name)

View File

@ -1,54 +1,10 @@
# 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
has_one :data, class_name: 'AlertsServiceData', autosave: true,
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
before_save :prevent_save
def self.to_param
'alerts'
@ -58,33 +14,15 @@ class AlertsService < Service
%w()
end
def data
super || build_data
end
private
def prevent_token_assignment
self.token = token_was if token.present? && token_changed?
end
def prevent_save
errors.add(:base, _('Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.'))
log_error('Prevented attempt to save or update deprecated AlertsService')
def ensure_token
self.token = generate_token if token.blank?
end
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
# Stops execution of callbacks and database operation while
# preserving expectations of #save (will not raise) & #save! (raises)
# https://guides.rubyonrails.org/active_record_callbacks.html#halting-execution
throw :abort # rubocop:disable Cop/BanCatchThrow
end
end

View File

@ -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

View File

@ -19,7 +19,6 @@ class Service < ApplicationRecord
PROJECT_SPECIFIC_SERVICE_NAMES = %w[
jenkins
alerts
].freeze
# Fake services to help with local development.

View File

@ -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

View File

@ -1 +0,0 @@
.js-alerts-service-settings{ data: alerts_settings_data(disabled: true) }

View File

@ -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'

View File

@ -0,0 +1,5 @@
---
title: Show status of snippet author in header
merge_request: 51030
author: Kev @KevSlashNull
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Lower allocations when building nav
merge_request: 51628
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Remove deprecated generic alert integration in favor of HTTP Integrations
merge_request: 50913
author:
type: removed

View File

@ -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

View File

@ -0,0 +1 @@
d72cf1c88a060ccadd9f90cbef5ae7d4ea6a4416a6263d11a870e01b02d1f935

View File

@ -22981,7 +22981,6 @@ type ServiceEdge {
}
enum ServiceType {
ALERTS_SERVICE
ASANA_SERVICE
ASSEMBLA_SERVICE
BAMBOO_SERVICE

View File

@ -66648,12 +66648,6 @@
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "ALERTS_SERVICE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ASANA_SERVICE",
"description": null,

View File

@ -4921,7 +4921,6 @@ State of a Sentry error.
| Value | Description |
| ----- | ----------- |
| `ALERTS_SERVICE` | |
| `ASANA_SERVICE` | |
| `ASSEMBLA_SERVICE` | |
| `BAMBOO_SERVICE` | |

View File

@ -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)
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).

View File

@ -214,7 +214,7 @@ variables:
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_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
```
@ -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_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_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_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` |

View File

@ -161,7 +161,6 @@ module API
def self.services
{
'alerts' => [],
'asana' => [
{
required: true,
@ -807,7 +806,6 @@ module API
def self.service_classes
[
::AlertsService,
::AsanaService,
::AssemblaService,
::BambooService,

View File

@ -24,7 +24,9 @@ module Gitlab
Gitlab::CurrentSettings.snowplow_enabled?
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)
product_analytics.event(category, action, label: label, property: property, value: value, context: context)
end

View 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

View File

@ -174,6 +174,18 @@ module Gitlab
rescue IPAddr::InvalidAddressError
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.
# If the string is not a valid URI, it returns nil.
# Param uri_string should be a String object.

View File

@ -2760,7 +2760,7 @@ msgstr ""
msgid "Alerts"
msgstr ""
msgid "Alerts endpoint"
msgid "Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead."
msgstr ""
msgid "AlertsIntegrations|Alerts will be created through this integration"
@ -4151,9 +4151,6 @@ msgstr ""
msgid "Authorize %{user} to use your account?"
msgstr ""
msgid "Authorize external services to send alerts to GitLab"
msgstr ""
msgid "Authorized %{new_chat_name}"
msgstr ""
@ -32306,9 +32303,6 @@ msgstr ""
msgid "You can now export your security dashboard to a CSV report."
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."
msgstr ""

View File

@ -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

View File

@ -38,24 +38,6 @@ FactoryBot.define do
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
project
active { true }

View File

@ -32,8 +32,8 @@ FactoryBot.define do
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
create(:project_error_tracking_setting, project: projects[0])
create(:project_error_tracking_setting, project: projects[1], enabled: false)
create(:alerts_service, project: projects[0])
create(:alerts_service, :inactive, project: projects[1])
create(:service, project: projects[0], type: 'AlertsService', active: true)
create(:service, project: projects[1], type: 'AlertsService', active: false)
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)
issues = create_list(:issue, 4, project: projects[0])

View File

@ -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

View File

@ -17,6 +17,8 @@ describe('Snippet header component', () => {
let err;
const originalRelativeUrlRoot = gon.relative_url_root;
const GlEmoji = { template: '<img/>' };
function createComponent({
loading = false,
permissions = {},
@ -47,10 +49,15 @@ describe('Snippet header component', () => {
},
stubs: {
ApolloMutation,
GlEmoji,
},
});
}
const findAuthorEmoji = () => wrapper.find(GlEmoji);
const findAuthoredMessage = () => wrapper.find('[data-testid="authored-message"]').text();
const buttonCount = () => wrapper.findAll(GlButton).length;
beforeEach(() => {
gon.relative_url_root = '/foo/';
snippet = {
@ -66,6 +73,7 @@ describe('Snippet header component', () => {
project: null,
author: {
name: 'Thor Odinson',
status: null,
},
blobs: [Blob],
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', () => {
createComponent();
const text = wrapper.find('[data-testid="authored-message"]').text();
const text = findAuthoredMessage();
expect(text).toContain('Authored 1 month ago by');
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', () => {
snippet.author = null;
createComponent();
const text = wrapper.find('[data-testid="authored-message"]').text();
const text = findAuthoredMessage();
expect(text).toBe('Authored 1 month ago');
});
@ -121,7 +148,7 @@ describe('Snippet header component', () => {
updateSnippet: false,
},
});
expect(wrapper.findAll(GlButton).length).toEqual(0);
expect(buttonCount()).toEqual(0);
createComponent({
permissions: {
@ -129,7 +156,7 @@ describe('Snippet header component', () => {
updateSnippet: false,
},
});
expect(wrapper.findAll(GlButton).length).toEqual(1);
expect(buttonCount()).toEqual(1);
createComponent({
permissions: {
@ -137,7 +164,7 @@ describe('Snippet header component', () => {
updateSnippet: true,
},
});
expect(wrapper.findAll(GlButton).length).toEqual(2);
expect(buttonCount()).toEqual(2);
createComponent({
permissions: {
@ -149,7 +176,7 @@ describe('Snippet header component', () => {
canCreateSnippet: true,
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlButton).length).toEqual(3);
expect(buttonCount()).toEqual(3);
});
});

View File

@ -525,7 +525,6 @@ project:
- designs
- project_aliases
- external_pull_requests
- alerts_service
- grafana_integration
- remove_source_branch_after_merge
- deleting_user

View 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

View File

@ -41,21 +41,42 @@ RSpec.describe Gitlab::Tracking do
allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
end
it 'delegates to snowplow destination' do
expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
.to receive(:event)
.with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
shared_examples 'delegates to destination' do |klass|
context 'with 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)
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)
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)
end
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
include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
end
describe '.self_describing_event' do

View File

@ -392,6 +392,23 @@ RSpec.describe Gitlab::Utils do
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
it 'returns Addressable::URI object' do
expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI)

View 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

View File

@ -2,108 +2,38 @@
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
let_it_be(:project) { create(:project) }
let(:service_params) { { project: project, active: active } }
let(:active) { true }
let(:service) { described_class.new(service_params) }
subject(:service) { described_class.new(project: project) }
shared_context 'when active' do
let(:active) { true }
it { is_expected.to be_valid }
describe '#to_param' do
subject { service.to_param }
it { is_expected.to eq('alerts') }
end
shared_context 'when inactive' do
let(:active) { false }
describe '#supported_events' do
subject { service.supported_events }
it { is_expected.to be_empty }
end
shared_context 'when persisted' do
before do
service.save!
service.reload
end
end
describe '#save' do
it 'prevents records from being created or updated' do
expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
hash_including(message: 'Prevented attempt to save or update deprecated AlertsService')
)
describe '#url' do
include Gitlab::Routing
expect(service.save).to be_falsey
subject { service.url }
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'
expect(service.errors.full_messages).to include(
'Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.'
)
end
end
end

View File

@ -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