Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-25 09:08:30 +00:00
parent 0ebaa8a2fd
commit 03c3f9f501
35 changed files with 312 additions and 223 deletions

View file

@ -277,6 +277,7 @@ export default {
<h5
ref="graphTitle"
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate append-right-8"
tabindex="0"
>
{{ title }}
</h5>

View file

@ -52,10 +52,17 @@ export default {
</script>
<template>
<div v-if="showPanels" ref="graph-group" class="card prometheus-panel">
<div v-if="showPanels" ref="graph-group" class="card prometheus-panel" tabindex="0">
<div class="card-header d-flex align-items-center">
<h4 class="flex-grow-1">{{ name }}</h4>
<a role="button" class="js-graph-group-toggle" @click="collapse">
<a
data-testid="group-toggle-button"
role="button"
class="js-graph-group-toggle gl-text-gray-900"
tabindex="0"
@click="collapse"
@keyup.enter="collapse"
>
<icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" />
</a>
</div>

View file

@ -754,7 +754,8 @@
margin-right: 10px;
min-width: 0;
.issue-weight-icon {
.issue-weight-icon,
.issue-estimate-icon {
vertical-align: sub;
}
}

View file

@ -135,6 +135,10 @@ module AlertManagement
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
end
def register_new_event!
increment!(:events, 1)
end
private
def hosts_length

View file

@ -435,15 +435,7 @@ class JiraService < IssueTrackerService
yield
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
@error = error
log_error(
"Error sending message",
client_url: client_url,
error: {
exception_class: error.class.name,
exception_message: error.message,
exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(error.backtrace)
}
)
log_error("Error sending message", client_url: client_url, error: @error.message)
nil
end

View file

@ -10,7 +10,7 @@ module Projects
return forbidden unless alerts_service_activated?
return unauthorized unless valid_token?(token)
alert = create_alert
alert = process_alert
return bad_request unless alert.persisted?
process_incident_issues(alert) if process_issues?
@ -26,13 +26,29 @@ module Projects
delegate :alerts_service, :alerts_service_activated?, to: :project
def am_alert_params
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
strong_memoize(:am_alert_params) do
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
end
end
def process_alert
if alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
alert.register_new_event!
else
create_alert
end
end
def create_alert
AlertManagement::Alert.create(am_alert_params)
end
def find_alert_by_fingerprint(fingerprint)
return unless fingerprint
AlertManagement::Alert.for_fingerprint(project, fingerprint).first
end
def send_email?
incident_management_setting.send_email?
end

View file

@ -43,6 +43,7 @@
= link_to_label(label, small: true)
= render_if_exists "projects/issues/issue_weight", issue: issue
= render "projects/issues/issue_estimate", issue: issue
.issuable-meta
%ul.controls

View file

@ -0,0 +1,7 @@
- issue = local_assigns.fetch(:issue)
- if issue.time_estimate > 0
%span.issuable-estimate.d-none.d-sm-inline-block.has-tooltip{ data: { container: 'body', qa_selector: 'issuable_estimate' }, title: _('Estimate') }
&nbsp;
= sprite_icon('timer', size: 16, css_class: 'issue-estimate-icon')
= Gitlab::TimeTrackingFormatter.output(issue.time_estimate)

View file

@ -0,0 +1,5 @@
---
title: Set fingerprints and increment events count for Alert Management alerts
merge_request: 32613
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Less verbose JiraService error logs
merge_request: 32847
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Focus and toggle metrics dashboard panels via keyboard
merge_request: 28603
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Show estimate on issues list
merge_request: 28271
author: Lee Tickett
type: added

View file

@ -0,0 +1,6 @@
---
title: Update index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial index
for secret_detection
merge_request: 32584
author:
type: performance

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
class UpdateIndexCiBuildsOnCommitIdAndArtifactsExpireatandidpartial < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
OLD_INDEX_NAME = 'index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial'
NEW_INDEX_NAME = 'index_ci_builds_on_commit_id_artifacts_expired_at_and_id'
OLD_CLAUSE = "type::text = 'Ci::Build'::text AND (retried = false OR retried IS NULL) AND
(name::text = ANY (ARRAY['sast'::character varying,
'dependency_scanning'::character varying,
'sast:container'::character varying,
'container_scanning'::character varying,
'dast'::character varying]::text[]))"
NEW_CLAUSE = "type::text = 'Ci::Build'::text AND (retried = false OR retried IS NULL) AND
(name::text = ANY (ARRAY['sast'::character varying,
'secret_detection'::character varying,
'dependency_scanning'::character varying,
'container_scanning'::character varying,
'dast'::character varying]::text[]))"
def up
add_concurrent_index :ci_builds, [:commit_id, :artifacts_expire_at, :id], name: NEW_INDEX_NAME, where: NEW_CLAUSE
remove_concurrent_index_by_name :ci_builds, OLD_INDEX_NAME
end
def down
add_concurrent_index :ci_builds, [:commit_id, :artifacts_expire_at, :id], name: OLD_INDEX_NAME, where: OLD_CLAUSE
remove_concurrent_index_by_name :ci_builds, NEW_INDEX_NAME
end
end

View file

@ -9229,8 +9229,6 @@ CREATE INDEX index_ci_builds_on_artifacts_expire_at ON public.ci_builds USING bt
CREATE INDEX index_ci_builds_on_auto_canceled_by_id ON public.ci_builds USING btree (auto_canceled_by_id);
CREATE INDEX index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial ON public.ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
CREATE INDEX index_ci_builds_on_commit_id_and_stage_idx_and_created_at ON public.ci_builds USING btree (commit_id, stage_idx, created_at);
CREATE INDEX index_ci_builds_on_commit_id_and_status_and_type ON public.ci_builds USING btree (commit_id, status, type);
@ -9239,6 +9237,8 @@ CREATE INDEX index_ci_builds_on_commit_id_and_type_and_name_and_ref ON public.ci
CREATE INDEX index_ci_builds_on_commit_id_and_type_and_ref ON public.ci_builds USING btree (commit_id, type, ref);
CREATE INDEX index_ci_builds_on_commit_id_artifacts_expired_at_and_id ON public.ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
CREATE INDEX index_ci_builds_on_project_id_and_id ON public.ci_builds USING btree (project_id, id);
CREATE INDEX index_ci_builds_on_project_id_and_name_and_ref ON public.ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL)));
@ -13922,5 +13922,6 @@ COPY "schema_migrations" (version) FROM STDIN;
20200514000340
20200515155620
20200519115908
20200519171058
\.

View file

@ -41,8 +41,11 @@ are:
process jobs using a [FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) queue.
A Runner that is specific only runs for the specified project(s). A shared Runner
can run jobs for every project that has enabled the option **Allow shared Runners**
under **Settings > CI/CD**.
can run jobs for every project that allows shared Runners. To allow shared Runners on a project:
1. Navigate to the project's **{settings}** **Settings > CI/CD**.
1. Expand the **Runners** section.
1. Click on **Allow shared Runners**.
Projects with high demand of CI activity can also benefit from using specific
Runners. By having dedicated Runners you are guaranteed that the Runner is not
@ -68,7 +71,7 @@ You can only register a shared Runner if you are an admin of the GitLab instance
Shared Runners are enabled by default as of GitLab 8.2, but can be disabled
with the **Disable shared Runners** button which is present under each project's
**Settings > CI/CD** page. Previous versions of GitLab defaulted shared
**{settings}** **Settings > CI/CD** page. Previous versions of GitLab defaulted shared
Runners to disabled.
## Registering a specific Runner
@ -83,7 +86,7 @@ Registering a specific Runner can be done in two ways:
To create a specific Runner without having admin rights to the GitLab instance,
visit the project you want to make the Runner work for in GitLab:
1. Go to **Settings > CI/CD** to obtain the token
1. Go to **{settings}** **Settings > CI/CD** to obtain the token
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
## Registering a group Runner
@ -91,7 +94,7 @@ visit the project you want to make the Runner work for in GitLab:
Creating a group Runner requires Owner permissions for the group. To create a
group Runner visit the group you want to make the Runner work for in GitLab:
1. Go to **Settings > CI/CD** to obtain the token
1. Go to **{settings}** **Settings > CI/CD** to obtain the token
1. [Register the Runner](https://docs.gitlab.com/runner/register/)
### Making an existing shared Runner specific
@ -100,7 +103,7 @@ If you are an admin on your GitLab instance, you can turn any shared Runner into
a specific one, but not the other way around. Keep in mind that this is a one
way transition.
1. Go to the Runners in the **Admin Area > Overview > Runners** (`/admin/runners`)
1. Go to the Runners in the **{admin}** **Admin Area > Overview > Runners** (`/admin/runners`)
and find your Runner
1. Enable any projects under **Restrict projects for this Runner** to be used
with the Runner
@ -116,7 +119,7 @@ can be changed afterwards under each Runner's settings.
To lock/unlock a Runner:
1. Visit your project's **Settings > CI/CD**
1. Visit your project's **{settings}** **Settings > CI/CD**
1. Find the Runner you wish to lock/unlock and make sure it's enabled
1. Click the pencil button
1. Check the **Lock to current projects** option
@ -130,7 +133,7 @@ you can enable the Runner also on any other project where you have Owner permiss
To enable/disable a Runner in your project:
1. Visit your project's **Settings > CI/CD**
1. Visit your project's **{settings}** **Settings > CI/CD**
1. Find the Runner you wish to enable/disable
1. Click **Enable for this project** or **Disable for this project**
@ -164,7 +167,7 @@ Whenever a Runner is protected, the Runner picks only jobs created on
To protect/unprotect Runners:
1. Visit your project's **Settings > CI/CD**
1. Visit your project's **{settings}** **Settings > CI/CD**
1. Find a Runner you want to protect/unprotect and make sure it's enabled
1. Click the pencil button besides the Runner name
1. Check the **Protected** option
@ -254,7 +257,7 @@ Runner settings.
To make a Runner pick untagged jobs:
1. Visit your project's **Settings > CI/CD > Runners**.
1. Visit your project's **{settings}** **Settings > CI/CD > Runners**.
1. Find the Runner you want to pick untagged jobs and make sure it's enabled.
1. Click the pencil button.
1. Check the **Run untagged jobs** option.
@ -372,7 +375,7 @@ attacker.
To reset the token:
1. Go to **Settings > CI/CD** for a specified Project.
1. Go to **{settings}** **Settings > CI/CD** for a specified Project.
1. Expand the **General pipelines settings** section.
1. Find the **Runner token** form field and click the **Reveal value** button.
1. Delete the value and save the form.
@ -402,7 +405,7 @@ different places.
To view the IP address of a shared Runner you must have admin access to
the GitLab instance. To determine this:
1. Visit **Admin Area > Overview > Runners**
1. Visit **{admin}** **Admin Area > Overview > Runners**
1. Look for the Runner in the table and you should see a column for "IP Address"
![shared Runner IP address](img/shared_runner_ip_address.png)
@ -411,7 +414,7 @@ the GitLab instance. To determine this:
You can find the IP address of a Runner for a specific project by:
1. Visit your project's **Settings > CI/CD**
1. Visit your project's **{settings}** **Settings > CI/CD**
1. Find the Runner and click on it's ID which links you to the details page
1. On the details page you should see a row for "IP Address"

View file

@ -3880,11 +3880,11 @@ if using Git 2.10 or newer.
## Processing Git pushes
GitLab will create at most 4 branch and tags pipelines when
doing pushing multiple changes in single `git push` invocation.
GitLab will create at most 4 branch and tag pipelines when
pushing multiple changes in single `git push` invocation.
This limitation does not affect any of the updated Merge Request pipelines,
all updated Merge Requests will have a pipeline created when using
This limitation does not affect any of the updated Merge Request pipelines.
All updated Merge Requests will have a pipeline created when using
[pipelines for merge requests](../merge_request_pipelines/index.md).
## Deprecated parameters

View file

@ -28,7 +28,7 @@ To set up the generic alerts integration:
## Customizing the payload
You can customize the payload by sending the following parameters. All fields are optional:
You can customize the payload by sending the following parameters. All fields other than `title` are optional:
| Property | Type | Description |
| -------- | ---- | ----------- |
@ -39,6 +39,7 @@ You can customize the payload by sending the following parameters. All fields ar
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
TIP: **Payload size:**
Ensure your requests are smaller than the [payload application limits](../../../administration/instance_limits.md#generic-alert-json-payloads).
@ -65,5 +66,7 @@ Example payload:
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385"
}
```

View file

@ -20,7 +20,8 @@ module Gitlab
hosts: Array(annotations[:hosts]),
payload: payload,
started_at: parsed_payload['startsAt'],
severity: annotations[:severity]
severity: annotations[:severity],
fingerprint: annotations[:fingerprint]
}
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Gitlab
module AlertManagement
class Fingerprint
def self.generate(data)
new.generate(data)
end
def generate(data)
return unless data.present?
if data.is_a?(Array)
data = flatten_array(data)
end
Digest::SHA1.hexdigest(data.to_s)
end
private
def flatten_array(array)
array.flatten.map!(&:to_s).join
end
end
end
end

View file

@ -106,7 +106,7 @@ module Gitlab
end
def gitlab_fingerprint
Digest::SHA1.hexdigest(plain_gitlab_fingerprint)
Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
end
def valid?

View file

@ -35,6 +35,10 @@ module Gitlab
payload[:severity].presence || DEFAULT_SEVERITY
end
def fingerprint
Gitlab::AlertManagement::Fingerprint.generate(payload[:fingerprint])
end
def annotations
primary_params
.reverse_merge(flatten_secondary_params)
@ -49,7 +53,8 @@ module Gitlab
'monitoring_tool' => payload[:monitoring_tool],
'service' => payload[:service],
'hosts' => hosts.presence,
'severity' => severity
'severity' => severity,
'fingerprint' => fingerprint
}
end

View file

@ -12,12 +12,7 @@ module Gitlab
def request(*args)
result = make_request(*args)
unless result.response.is_a?(Net::HTTPSuccess)
Gitlab::ErrorTracking.track_and_raise_exception(
JIRA::HTTPError.new(result.response),
response: result.body
)
end
raise JIRA::HTTPError.new(result.response) unless result.response.is_a?(Net::HTTPSuccess)
result
end

View file

@ -8799,6 +8799,9 @@ msgstr ""
msgid "Errors:"
msgstr ""
msgid "Estimate"
msgstr ""
msgid "Estimated"
msgstr ""

View file

@ -35,6 +35,7 @@ Disallow: /snippets/*/raw
User-Agent: *
Disallow: /*/*.git
Disallow: /*/*/fork/new
Disallow: /*/-/archive/
Disallow: /*/*/repository/archive*
Disallow: /*/*/activity
Disallow: /*/*/new

View file

@ -126,6 +126,10 @@ describe('Dashboard Panel', () => {
expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
});
it('does not contain a tabindex attribute', () => {
expect(wrapper.find(MonitorEmptyChart).contains('[tabindex]')).toBe(false);
});
});
describe('When graphData is null', () => {

View file

@ -8,6 +8,7 @@ describe('Graph group component', () => {
const findGroup = () => wrapper.find({ ref: 'graph-group' });
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
const findCaretIcon = () => wrapper.find(Icon);
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
const createComponent = propsData => {
wrapper = shallowMount(GraphGroup, {
@ -41,6 +42,16 @@ describe('Graph group component', () => {
});
});
it('should contain a tabindex', () => {
expect(findGroup().contains('[tabindex]')).toBe(true);
});
it('should contain a tab index for the collapse button', () => {
const groupToggle = findToggleButton();
expect(groupToggle.contains('[tabindex]')).toBe(true);
});
it('should show the open the group when collapseGroup is set to true', () => {
wrapper.setProps({
collapseGroup: true,
@ -69,6 +80,15 @@ describe('Graph group component', () => {
expect(wrapper.vm.caretIcon).toBe('angle-down');
});
it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
const graphGroupContent = findContent();
const button = findToggleButton();
button.trigger('keyup.enter');
expect(graphGroupContent.isVisible()).toBe(false);
});
});
describe('When groups can not be collapsed', () => {

View file

@ -1,166 +0,0 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import testAction from './vuex_action_helper';
describe('VueX test helper (testAction)', () => {
let originalExpect;
let assertion;
let mock;
const noop = () => {};
beforeAll(() => {
mock = new MockAdapter(axios);
/*
In order to test the helper properly, we need to overwrite the jasmine `expect` helper.
We test that the testAction helper properly passes the dispatched actions/committed mutations
to the jasmine helper.
*/
originalExpect = expect;
assertion = null;
global.expect = actual => ({
toEqual: () => {
originalExpect(actual).toEqual(assertion);
},
});
});
afterAll(() => {
mock.restore();
global.expect = originalExpect;
});
it('should properly pass on state and payload', () => {
const exampleState = { FOO: 12, BAR: 3 };
const examplePayload = { BAZ: 73, BIZ: 55 };
const action = ({ state }, payload) => {
originalExpect(state).toEqual(exampleState);
originalExpect(payload).toEqual(examplePayload);
};
assertion = { mutations: [], actions: [] };
testAction(action, examplePayload, exampleState);
});
describe('should work with synchronous actions', () => {
it('committing mutation', () => {
const action = ({ commit }) => {
commit('MUTATION');
};
assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
});
it('dispatching action', () => {
const action = ({ dispatch }) => {
dispatch('ACTION');
};
assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
});
it('work with jasmine done once finished', done => {
assertion = { mutations: [], actions: [] };
testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
});
it('provide promise interface', done => {
assertion = { mutations: [], actions: [] };
testAction(noop, null, {}, assertion.mutations, assertion.actions)
.then(done)
.catch(done.fail);
});
});
describe('should work with promise based actions (fetch action)', () => {
let lastError;
const data = { FOO: 'BAR' };
const promiseAction = ({ commit, dispatch }) => {
dispatch('ACTION');
return axios
.get(TEST_HOST)
.catch(error => {
commit('ERROR');
lastError = error;
throw error;
})
.then(() => {
commit('SUCCESS');
return data;
});
};
beforeEach(() => {
lastError = null;
});
it('work with jasmine done once finished', done => {
mock.onGet(TEST_HOST).replyOnce(200, 42);
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done);
});
it('return original data of successful promise while checking actions/mutations', done => {
mock.onGet(TEST_HOST).replyOnce(200, 42);
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
.then(res => {
originalExpect(res).toEqual(data);
done();
})
.catch(done.fail);
});
it('return original error of rejected promise while checking actions/mutations', done => {
mock.onGet(TEST_HOST).replyOnce(500, '');
assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
.then(done.fail)
.catch(error => {
originalExpect(error).toBe(lastError);
done();
});
});
});
it('should work with async actions not returning promises', done => {
const data = { FOO: 'BAR' };
const promiseAction = ({ commit, dispatch }) => {
dispatch('ACTION');
axios
.get(TEST_HOST)
.then(() => {
commit('SUCCESS');
return data;
})
.catch(error => {
commit('ERROR');
throw error;
});
};
mock.onGet(TEST_HOST).replyOnce(200, 42);
assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done);
});
});

View file

@ -32,7 +32,8 @@ describe Gitlab::AlertManagement::AlertParams do
severity: 'critical',
hosts: ['gitlab.com'],
payload: payload,
started_at: started_at
started_at: started_at,
fingerprint: nil
)
end

View file

@ -0,0 +1,48 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::AlertManagement::Fingerprint do
using RSpec::Parameterized::TableSyntax
let_it_be(:alert) { create(:alert_management_alert) }
describe '.generate' do
subject { described_class.generate(data) }
context 'when data is an array' do
let(:data) { [1, 'fingerprint', 'given'] }
it 'flattens the array' do
expect_next_instance_of(described_class) do |obj|
expect(obj).to receive(:flatten_array)
end
subject
end
it 'returns the hashed fingerprint' do
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
expect(subject).to eq(expected_fingerprint)
end
end
context 'when data is a non-array type' do
where(:data) do
[
111,
'fingerprint',
:fingerprint,
true,
{ test: true }
]
end
with_them do
it 'performs like a hashed fingerprint' do
expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
end
end
end
end
end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
describe Gitlab::Alerting::NotificationPayloadParser do
describe '.call' do
@ -89,6 +89,39 @@ describe Gitlab::Alerting::NotificationPayloadParser do
end
end
context 'with fingerprint' do
before do
payload[:fingerprint] = data
end
shared_examples 'fingerprint generation' do
it 'generates the fingerprint correctly' do
expect(result).to eq(Gitlab::AlertManagement::Fingerprint.generate(data))
end
end
context 'with blank fingerprint' do
it_behaves_like 'fingerprint generation' do
let(:data) { ' ' }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
context 'with fingerprint given' do
it_behaves_like 'fingerprint generation' do
let(:data) { 'fingerprint' }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
context 'with array fingerprint given' do
it_behaves_like 'fingerprint generation' do
let(:data) { [1, 'fingerprint', 'given'] }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
end
context 'when payload attributes have blank lines' do
let(:payload) do
{

View file

@ -317,4 +317,14 @@ describe AlertManagement::Alert do
expect { subject }.to change { alert.reload.ended_at }.to nil
end
end
describe '#register_new_event!' do
subject { alert.register_new_event! }
let(:alert) { create(:alert_management_alert) }
it 'increments the events count by 1' do
expect { subject }.to change { alert.events}.by(1)
end
end
end

View file

@ -688,22 +688,18 @@ describe JiraService do
context 'when the test fails' do
it 'returns result with the error' do
test_url = 'http://jira.example.com/rest/api/2/serverInfo'
error_message = 'Some specific failure.'
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
.to_raise(JIRA::HTTPError.new(double(message: 'Some specific failure.')))
.to_raise(JIRA::HTTPError.new(double(message: error_message)))
expect(jira_service).to receive(:log_error).with(
"Error sending message",
hash_including(
client_url: url,
error: hash_including(
exception_class: 'JIRA::HTTPError',
exception_message: 'Some specific failure.'
)
)
'Error sending message',
client_url: 'http://jira.example.com',
error: error_message
)
expect(jira_service.test(nil)).to eq(success: false, result: 'Some specific failure.')
expect(jira_service.test(nil)).to eq(success: false, result: error_message)
end
end
end

View file

@ -73,6 +73,7 @@ describe Projects::Alerting::NotifyService do
describe '#execute' do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
let(:fingerprint) { 'testing' }
let(:service) { described_class.new(project, nil, payload) }
let(:payload_raw) do
{
@ -82,7 +83,8 @@ describe Projects::Alerting::NotifyService do
monitoring_tool: 'GitLab RSpec',
service: 'GitLab Test Suite',
description: 'Very detailed description',
hosts: ['1.1.1.1', '2.2.2.2']
hosts: ['1.1.1.1', '2.2.2.2'],
fingerprint: fingerprint
}.with_indifferent_access
end
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
@ -131,11 +133,23 @@ describe Projects::Alerting::NotifyService do
description: payload_raw.fetch(:description),
monitoring_tool: payload_raw.fetch(:monitoring_tool),
service: payload_raw.fetch(:service),
fingerprint: nil,
fingerprint: Digest::SHA1.hexdigest(fingerprint),
ended_at: nil
)
end
context 'existing alert with same fingerprint' do
let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: Digest::SHA1.hexdigest(fingerprint)) }
it 'does not create AlertManagement::Alert' do
expect { subject }.not_to change(AlertManagement::Alert, :count)
end
it 'increments the existing alert count' do
expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
end
end
context 'with a minimal payload' do
let(:payload_raw) do
{