Commit 0edb497d authored by Simon Knox's avatar Simon Knox Committed by Bob Van Landuyt

Add AlertsService to projects

This adds a generic alerts service to a project's settings. Enabling it generates a new token to be used when calling the alert service.
parent a1abe761
......@@ -218,6 +218,12 @@ ActiveRecordAssociationReload:
- 'spec/**/*'
- 'ee/spec/**/*'
Naming/PredicateName:
Enabled: true
Exclude:
- 'spec/**/*'
- 'ee/spec/**/*'
RSpec/FactoriesInMigrationSpecs:
Enabled: true
Include:
......
......@@ -39,10 +39,13 @@ module EE
has_one :project_registry, class_name: 'Geo::ProjectRegistry', inverse_of: :project
has_one :push_rule, ->(project) { project&.feature_available?(:push_rules) ? all : none }
has_one :index_status
has_one :jenkins_service
has_one :jenkins_deprecated_service
has_one :github_service
has_one :gitlab_slack_application_service
has_one :alerts_service
has_one :tracing_setting, class_name: 'ProjectTracingSetting'
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
......@@ -487,17 +490,11 @@ module EE
override :disabled_services
def disabled_services
strong_memoize(:disabled_services) do
disabled_services = []
unless feature_available?(:jenkins_integration)
disabled_services.push('jenkins', 'jenkins_deprecated')
end
unless feature_available?(:github_project_service_integration)
disabled_services.push('github')
[].tap do |services|
services.push('jenkins', 'jenkins_deprecated') unless feature_available?(:jenkins_integration)
services.push('github') unless feature_available?(:github_project_service_integration)
services.push('alerts') unless alerts_service_available?
end
disabled_services
end
end
......@@ -613,6 +610,11 @@ module EE
@design_repository ||= DesignManagement::Repository.new(self)
end
def alerts_service_available?
::Feature.enabled?(:generic_alert_endpoint, self) &&
feature_available?(:incident_management)
end
def package_already_taken?(package_name)
namespace.root_ancestor.all_projects
.joins(:packages)
......
......@@ -13,6 +13,7 @@ module EE
github
jenkins
jenkins_deprecated
alerts
]
if ::Gitlab.dev_env_or_com?
......
# frozen_string_literal: true
require 'securerandom'
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?
def url
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
_('Receive alerts on GitLab from any source')
end
def detailed_description
description
end
def self.to_param
'alerts'
end
def self.supported_events
%w()
end
def data
super || build_data
end
private
def prevent_token_assignment
self.token = token_was if token.present? && token_changed?
end
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
end
# 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'
end
- return unless Feature.enabled?(:generic_alert_endpoint, @project)
- return unless @project.alerts_service_available?
.js-alerts-service-settings{ data: { activated: @service.activated?.to_s,
form_path: project_service_path(@project, @service.to_param),
......
---
title: Add Alerts Service to Projects
merge_request: 16117
author:
type: added
......@@ -81,6 +81,7 @@ module EE
::GithubService,
::JenkinsService,
::JenkinsDeprecatedService,
::AlertsService,
*super
]
end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'User activates Alerts' do
set(:project) { create(:project) }
set(:user) { create(:user) }
let(:service_name) { 'alerts' }
let(:service_title) { 'Alerts endpoint' }
before do
sign_in(user)
project.add_maintainer(user)
end
shared_examples 'no service' do
it 'cannot see the service' do
visit_project_services
expect(page).not_to have_link(service_title)
end
end
context 'when feature available', :js do
before do
stub_licensed_features(incident_management: true)
stub_feature_flags(generic_alert_endpoint: true)
end
context 'when service is deactivated' do
it 'activates service' do
visit_project_services
expect(page).to have_link(service_title)
click_link(service_title)
expect(page).not_to have_active_service
click_activate_service
wait_for_requests
expect(page).to have_active_service
end
end
context 'when service is activated' do
before do
visit_alerts_service
click_activate_service
end
it 're-generates key' do
expect(reset_key.value).to be_blank
click_reset_key
click_confirm_reset_key
wait_for_requests
expect(reset_key.value).to be_present
end
end
context 'when feature flag `generic_alert_endpoint` disabled' do
before do
stub_feature_flags(generic_alert_endpoint: false)
end
it_behaves_like 'no service'
end
end
context 'when feature unavailable' do
before do
stub_licensed_features(incident_management: false)
end
it_behaves_like 'no service'
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 click_activate_service
find('#activated').click
end
def click_reset_key
click_button('Reset key')
end
def click_confirm_reset_key
within '.modal-content' do
click_reset_key
end
end
def reset_key
find_field('Authorization key')
end
def have_active_service
have_selector('.js-service-active-status[data-value="true"]')
end
end
# frozen_string_literal: true
require 'spec_helper'
describe AlertsService do
set(:project) { create(:project) }
let(:service_params) { { project: project, active: active } }
let(:active) { true }
let(:service) { described_class.new(service_params) }
shared_context 'when active' do
let(:active) { true }
end
shared_context 'when inactive' do
let(:active) { false }
end
shared_context 'when persisted' do
before do
service.save!
service.reload
end
end
describe '#url' do
include Gitlab::Routing
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'
end
end
end
......@@ -970,6 +970,7 @@ describe Project do
where(:license_feature, :disabled_services) do
:jenkins_integration | %w(jenkins jenkins_deprecated)
:github_project_service_integration | %w(github)
:incident_management | %w(alerts)
end
with_them do
......@@ -989,6 +990,20 @@ describe Project do
it { is_expected.to include(*disabled_services) }
end
end
context 'when incident_management is available' do
before do
stub_licensed_features(incident_management: true)
end
context 'when feature flag generic_alert_endpoint is disabled' do
before do
stub_feature_flags(generic_alert_endpoint: false)
end
it { is_expected.to include('alerts') }
end
end
end
describe '#pull_mirror_available?' do
......
......@@ -4,6 +4,15 @@ require 'spec_helper'
describe Service do
describe 'Available services' do
it { expect(described_class.available_services_names).to include("jenkins", "jira") }
let(:ee_services) do
%w(
github
jenkins
jenkins_deprecated
alerts
)
end
it { expect(described_class.available_services_names).to include(*ee_services) }
end
end
......@@ -155,6 +155,7 @@ module API
def self.services
{
'alerts' => [],
'asana' => [
{
required: true,
......@@ -696,6 +697,7 @@ module API
def self.service_classes
[
::AlertsService,
::AsanaService,
::AssemblaService,
::BambooService,
......
......@@ -1234,6 +1234,9 @@ msgstr ""
msgid "Alerts"
msgstr ""
msgid "Alerts endpoint"
msgstr ""
msgid "All"
msgstr ""
......@@ -12760,6 +12763,9 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
msgid "Receive alerts on GitLab from any source"
msgstr ""
msgid "Receive notifications about your own activity"
msgstr ""
......
......@@ -411,6 +411,7 @@ project:
- project_aliases
- external_pull_requests
- pages_metadatum
- alerts_service
award_emoji:
- awardable
- user
......
......@@ -28,12 +28,17 @@ Service.available_services_names.each do |service|
end
end
let(:licensed_features) do
{
'github' => :github_project_service_integration,
'jenkins' => :jenkins_integration,
'jenkins_deprecated' => :jenkins_integration,
'alerts' => :incident_management
}
end
before do
if service == 'github' && respond_to?(:stub_licensed_features)
stub_licensed_features(github_project_service_integration: true)
project.clear_memoization(:disabled_services)
project.clear_memoization(:licensed_feature_available)
end
enable_license_for_service(service)
end
def initialize_service(service)
......@@ -42,5 +47,18 @@ Service.available_services_names.each do |service|
service_item.save!
service_item
end
private
def enable_license_for_service(service)
return unless respond_to?(:stub_licensed_features)
licensed_feature = licensed_features[service]
return unless licensed_feature
stub_licensed_features(licensed_feature => true)
project.clear_memoization(:disabled_services)
project.clear_memoization(:licensed_feature_available)
end
end
end
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