Skip to content
Snippets Groups Projects
Commit cf1d4237 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 0ac82f99
No related branches found
No related tags found
No related merge requests found
Showing
with 511 additions and 17 deletions
- templates = []
- setting = project_incident_management_setting
- templates = setting.available_issue_templates.map { |t| [t.name, t.key] }
%section.settings.no-animate.js-incident-management-settings
.settings-header
%h4= _('Incidents')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p
= _('Action to take when receiving an alert.')
= link_to help_page_path('user/project/integrations/prometheus', anchor: 'taking-action-on-an-alert-ultimate') do
= _('More information')
.settings-content
= form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
= form_errors(@project.incident_management_setting)
.form-group
= f.fields_for :incident_management_setting_attributes, setting do |form|
.form-group
= form.check_box :create_issue
= form.label :create_issue, _('Create an issue. Issues are created for each alert triggered.'), class: 'form-check-label'
.form-group.col-sm-8
= form.label :issue_template_key, class: 'label-bold' do
= _('Issue template (optional)')
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'creating-issue-templates'), target: '_blank', rel: 'noopener noreferrer'
.select-wrapper
= form.select :issue_template_key, templates, {include_blank: 'No template selected'}, class: "form-control select-control"
= icon('chevron-down')
.form-group
= form.check_box :send_email
= form.label :send_email, _('Send a separate email notification to Developers.'), class: 'form-check-label'
= f.submit _('Save changes'), class: 'btn btn-success'
Loading
Loading
@@ -2,7 +2,7 @@
- page_title _('Operations Settings')
- breadcrumb_title _('Operations Settings')
 
= render_if_exists 'projects/settings/operations/incidents'
= render 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/external_dashboard'
= render 'projects/settings/operations/grafana_integration'
Loading
Loading
---
title: Dark syntax highlighting theme for Web IDE
merge_request: 24158
author:
type: added
---
title: Move Settings->Operations->Incidents to the Core
merge_request: 24600
author:
type: changed
---
title: Makes the generic alerts endpoint available with the free tier
merge_request: 23339
author:
type: changed
---
title: Refactor error tracking specs and add validation to enabled field in error tracking model
merge_request: 24892
author: Rajendra Kadam
type: added
# Generic alerts integration **(ULTIMATE)**
# Generic alerts integration
 
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
 
GitLab can accept alerts from any source via a generic webhook receiver.
When you set up the generic alerts integration, a unique endpoint will
Loading
Loading
Loading
Loading
@@ -161,6 +161,7 @@ module API
 
def self.services
{
'alerts' => [],
'asana' => [
{
required: true,
Loading
Loading
@@ -729,6 +730,7 @@ module API
 
def self.service_classes
[
::AlertsService,
::AsanaService,
::AssemblaService,
::BambooService,
Loading
Loading
Loading
Loading
@@ -8,6 +8,10 @@ module Gitlab
class << self
include Gitlab::Utils::StrongMemoize
 
QUERY_PATTERN = '(?<query>\?[a-zA-Z0-9%.()+_=-]+(&[a-zA-Z0-9%.()+_=-]+)*)?'
ANCHOR_PATTERN = '(?<anchor>\#[a-z0-9_-]+)?'
OPTIONAL_DASH_PATTERN = '(?:/-)?'
# Matches urls for a metrics dashboard. This could be
# either the /metrics endpoint or the /metrics_dashboard
# endpoint.
Loading
Loading
@@ -63,10 +67,10 @@ module Gitlab
(?<url>
#{gitlab_host_pattern}
#{project_path_pattern}
(?:/-)?
#{OPTIONAL_DASH_PATTERN}
#{path_suffix_pattern}
#{query_pattern}
#{anchor_pattern}
#{QUERY_PATTERN}
#{ANCHOR_PATTERN}
)
}x
end
Loading
Loading
@@ -78,14 +82,6 @@ module Gitlab
def project_path_pattern
"\/#{Project.reference_pattern}"
end
def query_pattern
'(?<query>\?[a-zA-Z0-9%.()+_=-]+(&[a-zA-Z0-9%.()+_=-]+)*)?'
end
def anchor_pattern
'(?<anchor>\#[a-z0-9_-]+)?'
end
end
end
end
Loading
Loading
Loading
Loading
@@ -87,6 +87,7 @@ module Gitlab
issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
issues_using_zoom_quick_actions: count(ZoomMeeting.select(:issue_id).distinct),
issues_with_embedded_grafana_charts_approx: ::Gitlab::GrafanaEmbedUsageData.issue_count,
incident_issues: count(::Issue.authored(::User.alert_bot)),
keys: count(Key),
label_lists: count(List.label),
lfs_objects: count(LfsObject),
Loading
Loading
@@ -98,6 +99,7 @@ module Gitlab
projects_imported_from_github: count(Project.where(import_type: 'github')),
projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
projects_with_alerts_service_enabled: count(AlertsService.active),
protected_branches: count(ProtectedBranch),
releases: count(Release),
remote_mirrors: count(RemoteMirror),
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@ require 'spec_helper'
 
describe Projects::Settings::OperationsController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project, reload: true) { create(:project) }
 
before do
sign_in(user)
Loading
Loading
@@ -121,6 +121,74 @@ describe Projects::Settings::OperationsController do
end
end
 
context 'incident management' do
describe 'GET #show' do
context 'with existing setting' do
let!(:incident_management_setting) do
create(:project_incident_management_setting, project: project)
end
it 'loads existing setting' do
get :show, params: project_params(project)
expect(controller.helpers.project_incident_management_setting)
.to eq(incident_management_setting)
end
end
context 'without an existing setting' do
it 'builds a new setting' do
get :show, params: project_params(project)
expect(controller.helpers.project_incident_management_setting).to be_new_record
end
end
end
describe 'PATCH #update' do
let(:params) do
{
incident_management_setting_attributes: {
create_issue: 'false',
send_email: 'false',
issue_template_key: 'some-other-template'
}
}
end
it_behaves_like 'PATCHable'
context 'updating each incident management setting' do
let(:project) { create(:project) }
let(:new_incident_management_settings) { {} }
before do
project.add_maintainer(user)
end
shared_examples 'a gitlab tracking event' do |params, event_key|
it "creates a gitlab tracking event #{event_key}" do
new_incident_management_settings = params
expect(Gitlab::Tracking).to receive(:event)
.with('IncidentManagement::Settings', event_key, kind_of(Hash))
patch :update, params: project_params(project, incident_management_setting_attributes: new_incident_management_settings)
project.reload
end
end
it_behaves_like 'a gitlab tracking event', { create_issue: '1' }, 'enabled_issue_auto_creation_on_alerts'
it_behaves_like 'a gitlab tracking event', { create_issue: '0' }, 'disabled_issue_auto_creation_on_alerts'
it_behaves_like 'a gitlab tracking event', { issue_template_key: 'template' }, 'enabled_issue_template_on_alerts'
it_behaves_like 'a gitlab tracking event', { issue_template_key: nil }, 'disabled_issue_template_on_alerts'
it_behaves_like 'a gitlab tracking event', { send_email: '1' }, 'enabled_sending_emails'
it_behaves_like 'a gitlab tracking event', { send_email: '0' }, 'disabled_sending_emails'
end
end
end
context 'error tracking' do
describe 'GET #show' do
context 'with existing setting' do
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
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 '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
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
Loading
Loading
@@ -4,7 +4,7 @@ require 'spec_helper'
 
describe 'Projects > Settings > For a forked project', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:project) { create(:project, :repository, create_templates: :issue) }
let(:role) { :maintainer }
 
before do
Loading
Loading
@@ -22,6 +22,54 @@ describe 'Projects > Settings > For a forked project', :js do
end
 
describe 'Settings > Operations' do
describe 'Incidents' do
let(:create_issue) { 'Create an issue. Issues are created for each alert triggered.' }
let(:send_email) { 'Send a separate email notification to Developers.' }
before do
create(:project_incident_management_setting, send_email: true, project: project)
visit project_settings_operations_path(project)
wait_for_requests
click_expand_incident_management_button
end
it 'renders form for incident management' do
expect(page).to have_selector('h4', text: 'Incidents')
end
it 'sets correct default values' do
expect(find_field(create_issue)).not_to be_checked
expect(find_field(send_email)).to be_checked
end
it 'updates form values' do
check(create_issue)
template_select = find_field('Issue template')
template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email)
save_form
click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked
expect(page).to have_select('Issue template', selected: 'bug')
expect(find_field(send_email)).not_to be_checked
end
def click_expand_incident_management_button
within '.js-incident-management-settings' do
click_button('Expand')
end
end
def save_form
page.within "#edit_project_#{project.id}" do
click_on 'Save changes'
end
end
end
context 'error tracking settings form' do
let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' }
 
Loading
Loading
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AlertsServiceForm with default values renders "authorization-key" input 1`] = `"<gl-form-input-stub id=\\"authorization-key\\" readonly=\\"true\\" value=\\"abcedfg123\\"></gl-form-input-stub>"`;
exports[`AlertsServiceForm with default values renders "url" input 1`] = `"<gl-form-input-stub id=\\"url\\" readonly=\\"true\\" value=\\"https://gitlab.com/endpoint-url\\"></gl-form-input-stub>"`;
exports[`AlertsServiceForm with default values renders toggle button 1`] = `"<toggle-button-stub id=\\"activated\\"></toggle-button-stub>"`;
exports[`AlertsServiceForm with default values shows description and "Learn More" link 1`] = `"Each alert source must be authorized using the following URL and authorization key. <a href=\\"https://docs.gitlab.com/ee/user/project/integrations/generic_alerts.md\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Learn more</a> about configuring this endpoint to receive alerts."`;
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import AlertsServiceForm from '~/alerts_service_settings/components/alerts_service_form.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import createFlash from '~/flash';
jest.mock('~/flash');
const defaultProps = {
initialAuthorizationKey: 'abcedfg123',
formPath: 'http://invalid',
url: 'https://gitlab.com/endpoint-url',
learnMoreUrl: 'https://docs.gitlab.com/ee/user/project/integrations/generic_alerts.md',
initialActivated: false,
};
describe('AlertsServiceForm', () => {
let wrapper;
let mockAxios;
const createComponent = (props = defaultProps, { methods } = {}) => {
wrapper = shallowMount(AlertsServiceForm, {
propsData: {
...defaultProps,
...props,
},
methods,
});
};
const findUrl = () => wrapper.find('#url');
const findAuthorizationKey = () => wrapper.find('#authorization-key');
const findDescription = () => wrapper.find('p');
const findActiveStatusIcon = val =>
document.querySelector(`.js-service-active-status[data-value=${val.toString()}]`);
beforeEach(() => {
mockAxios = new MockAdapter(axios);
setFixtures(`
<div>
<span class="js-service-active-status fa fa-circle" data-value="true"></span>
<span class="js-service-active-status fa fa-power-off" data-value="false"></span>
</div>`);
});
afterEach(() => {
wrapper.destroy();
mockAxios.restore();
});
describe('with default values', () => {
beforeEach(() => {
createComponent();
});
it('renders "url" input', () => {
expect(findUrl().html()).toMatchSnapshot();
});
it('renders "authorization-key" input', () => {
expect(findAuthorizationKey().html()).toMatchSnapshot();
});
it('renders toggle button', () => {
expect(wrapper.find(ToggleButton).html()).toMatchSnapshot();
});
it('shows description and "Learn More" link', () => {
expect(findDescription().element.innerHTML).toMatchSnapshot();
});
});
describe('reset key', () => {
it('triggers resetKey method', () => {
const resetKey = jest.fn();
const methods = { resetKey };
createComponent(defaultProps, { methods });
wrapper.find(GlModal).vm.$emit('ok');
expect(resetKey).toHaveBeenCalled();
});
it('updates the authorization key on success', () => {
const formPath = 'some/path';
mockAxios.onPut(formPath, { service: { token: '' } }).replyOnce(200, { token: 'newToken' });
createComponent({ formPath });
return wrapper.vm.resetKey().then(() => {
expect(findAuthorizationKey().attributes('value')).toBe('newToken');
});
});
it('shows flash message on error', () => {
const formPath = 'some/path';
mockAxios.onPut(formPath).replyOnce(404);
createComponent({ formPath });
return wrapper.vm.resetKey().then(() => {
expect(findAuthorizationKey().attributes('value')).toBe(
defaultProps.initialAuthorizationKey,
);
expect(createFlash).toHaveBeenCalled();
});
});
});
describe('activate toggle', () => {
it('triggers toggleActivated method', () => {
const toggleActivated = jest.fn();
const methods = { toggleActivated };
createComponent(defaultProps, { methods });
wrapper.find(ToggleButton).vm.$emit('change', true);
expect(toggleActivated).toHaveBeenCalled();
});
describe('successfully completes', () => {
describe.each`
initialActivated | value
${false} | ${true}
${true} | ${false}
`(
'when initialActivated=$initialActivated and value=$value',
({ initialActivated, value }) => {
beforeEach(() => {
const formPath = 'some/path';
mockAxios
.onPut(formPath, { service: { active: value } })
.replyOnce(200, { active: value });
createComponent({ initialActivated, formPath });
return wrapper.vm.toggleActivated(value);
});
it(`updates toggle button value to ${value}`, () => {
expect(wrapper.find(ToggleButton).props('value')).toBe(value);
});
it('updates visible status icons', () => {
expect(findActiveStatusIcon(!value)).toHaveClass('d-none');
expect(findActiveStatusIcon(value)).not.toHaveClass('d-none');
});
},
);
});
describe('error is encountered', () => {
beforeEach(() => {
const formPath = 'some/path';
mockAxios.onPut(formPath).replyOnce(500);
});
it('restores previous value', () => {
createComponent({ initialActivated: false });
return wrapper.vm.toggleActivated(true).then(() => {
expect(wrapper.find(ToggleButton).props('value')).toBe(false);
});
});
});
});
});
Loading
Loading
@@ -5,6 +5,37 @@ require 'spec_helper'
describe ProjectsHelper do
include ProjectForksHelper
 
describe '#project_incident_management_setting' do
let(:project) { create(:project) }
before do
helper.instance_variable_set(:@project, project)
end
context 'when incident_management_setting exists' do
let(:project_incident_management_setting) do
create(:project_incident_management_setting, project: project)
end
it 'return project_incident_management_setting' do
expect(helper.project_incident_management_setting).to(
eq(project_incident_management_setting)
)
end
end
context 'when incident_management_setting does not exist' do
it 'builds incident_management_setting' do
setting = helper.project_incident_management_setting
expect(setting).not_to be_persisted
expect(setting.send_email).to be_falsey
expect(setting.create_issue).to be_truthy
expect(setting.issue_template_key).to be_nil
end
end
end
describe '#error_tracking_setting_project_json' do
let(:project) { create(:project) }
 
Loading
Loading
Loading
Loading
@@ -74,6 +74,7 @@ describe('Multi-file editor library', () => {
renderSideBySide: true,
renderLineHighlight: 'all',
hideCursorInOverviewRuler: false,
theme: 'vs white',
});
});
});
Loading
Loading
Loading
Loading
@@ -22,6 +22,10 @@ describe Gitlab::UsageData 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_list(:issue, 2, project: projects[0], author: User.alert_bot)
create_list(:issue, 2, project: projects[1], author: User.alert_bot)
create_list(:issue, 4, project: projects[0])
create(:zoom_meeting, project: projects[0], issue: projects[0].issues[0], issue_status: :added)
create_list(:zoom_meeting, 2, project: projects[0], issue: projects[0].issues[1], issue_status: :removed)
Loading
Loading
@@ -159,6 +163,7 @@ describe Gitlab::UsageData do
issues_with_associated_zoom_link
issues_using_zoom_quick_actions
issues_with_embedded_grafana_charts_approx
incident_issues
keys
label_lists
labels
Loading
Loading
@@ -183,6 +188,7 @@ describe Gitlab::UsageData do
projects_prometheus_active
projects_with_repositories_enabled
projects_with_error_tracking_enabled
projects_with_alerts_service_enabled
pages_domains
protected_branches
releases
Loading
Loading
@@ -220,10 +226,12 @@ describe Gitlab::UsageData do
expect(count_data[:projects_mattermost_active]).to eq(0)
expect(count_data[:projects_with_repositories_enabled]).to eq(3)
expect(count_data[:projects_with_error_tracking_enabled]).to eq(1)
expect(count_data[:projects_with_alerts_service_enabled]).to eq(1)
expect(count_data[:issues_created_from_gitlab_error_tracking_ui]).to eq(1)
expect(count_data[:issues_with_associated_zoom_link]).to eq(2)
expect(count_data[:issues_using_zoom_quick_actions]).to eq(3)
expect(count_data[:issues_with_embedded_grafana_charts_approx]).to eq(2)
expect(count_data[:incident_issues]).to eq(4)
 
expect(count_data[:clusters_enabled]).to eq(4)
expect(count_data[:project_clusters_enabled]).to eq(3)
Loading
Loading
Loading
Loading
@@ -19,6 +19,19 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
it { is_expected.to allow_value("http://gitlab.com/api/0/projects/project1/something").for(:api_url) }
it { is_expected.not_to allow_values("http://gitlab.com/api/0/projects/project1/something€").for(:api_url) }
 
it 'disallows non-booleans in enabled column' do
is_expected.not_to allow_value(
nil
).for(:enabled)
end
it 'allows booleans in enabled column' do
is_expected.to allow_value(
true,
false
).for(:enabled)
end
it 'rejects invalid api_urls' do
is_expected.not_to allow_values(
"https://replaceme.com/'><script>alert(document.cookie)</script>", # unsafe
Loading
Loading
Loading
Loading
@@ -5607,7 +5607,21 @@ describe Project do
 
subject { project.alerts_service_activated? }
 
it { is_expected.to be_falsey }
context 'when project has an activated alerts service' do
before do
create(:alerts_service, project: project)
end
it { is_expected.to be_truthy }
end
context 'when project has an inactive alerts service' do
before do
create(:alerts_service, :inactive, project: project)
end
it { is_expected.to be_falsey }
end
end
 
describe '#self_monitoring?' do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment