Commit 141faaac authored by Felipe Artur's avatar Felipe Artur

Mattermost Notifications Service

parent 2d1dfae9
...@@ -95,6 +95,7 @@ class Project < ActiveRecord::Base ...@@ -95,6 +95,7 @@ class Project < ActiveRecord::Base
has_one :asana_service, dependent: :destroy has_one :asana_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy
has_one :mattermost_slash_commands_service, dependent: :destroy has_one :mattermost_slash_commands_service, dependent: :destroy
has_one :mattermost_service, dependent: :destroy
has_one :slack_service, dependent: :destroy has_one :slack_service, dependent: :destroy
has_one :buildkite_service, dependent: :destroy has_one :buildkite_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
......
require 'slack-notifier' require 'slack-notifier'
class SlackService module ChatMessage
class BaseMessage class BaseMessage
def initialize(params) def initialize(params)
raise NotImplementedError raise NotImplementedError
......
class SlackService module ChatMessage
class BuildMessage < BaseMessage class BuildMessage < BaseMessage
attr_reader :sha attr_reader :sha
attr_reader :ref_type attr_reader :ref_type
......
class SlackService module ChatMessage
class IssueMessage < BaseMessage class IssueMessage < BaseMessage
attr_reader :user_name attr_reader :user_name
attr_reader :title attr_reader :title
......
class SlackService module ChatMessage
class MergeMessage < BaseMessage class MergeMessage < BaseMessage
attr_reader :user_name attr_reader :user_name
attr_reader :project_name attr_reader :project_name
......
class SlackService module ChatMessage
class NoteMessage < BaseMessage class NoteMessage < BaseMessage
attr_reader :message attr_reader :message
attr_reader :user_name attr_reader :user_name
......
class SlackService module ChatMessage
class PipelineMessage < BaseMessage class PipelineMessage < BaseMessage
attr_reader :ref_type, :ref, :status, :project_name, :project_url, attr_reader :ref_type, :ref, :status, :project_name, :project_url,
:user_name, :duration, :pipeline_id :user_name, :duration, :pipeline_id
......
class SlackService module ChatMessage
class PushMessage < BaseMessage class PushMessage < BaseMessage
attr_reader :after attr_reader :after
attr_reader :before attr_reader :before
......
class SlackService module ChatMessage
class WikiPageMessage < BaseMessage class WikiPageMessage < BaseMessage
attr_reader :user_name attr_reader :user_name
attr_reader :title attr_reader :title
......
# Base class for Chat services # Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from. # This class is not meant to be used directly, but only to inherrit from.
class ChatService < Service class ChatService < Service
include ChatMessage
default_value_for :category, 'chat' default_value_for :category, 'chat'
has_many :chat_names, foreign_key: :service_id prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
# Custom serialized properties initialization
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
def valid_token?(token) if properties.nil?
self.respond_to?(:token) && self.properties = {}
self.token.present? && self.notify_only_broken_builds = true
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) self.notify_only_broken_pipelines = true
end
end
def can_test?
valid?
end end
def supported_events def supported_events
[] %w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page]
end end
def trigger(params) def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
object_kind = data[:object_kind]
data = data.merge(
project_url: project_url,
project_name: project_name
)
# WebHook events often have an 'update' event that follows a 'open' or
# 'close' action. Ignore update events for now to prevent duplicate
# messages from arriving.
message = get_message(object_kind, data)
return false unless message
opt = {}
opt[:channel] = get_channel_field(object_kind).presence || channel || default_channel
opt[:username] = username if username
notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
true
end
def event_channel_names
supported_events.map { |event| event_channel_name(event) }
end
def event_field(event)
fields.find { |field| field[:name] == event_channel_name(event) }
end
def global_fields
fields.reject { |field| field[:name].end_with?('channel') }
end
def default_channel
raise NotImplementedError raise NotImplementedError
end end
private
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
PushMessage.new(data)
when "issue"
IssueMessage.new(data) unless is_update?(data)
when "merge_request"
MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
WikiPageMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name)
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel }
end
end
def event_channel_name(event)
"#{event}_channel"
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
def project_url
project.web_url
end
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end
end end
class MattermostService < ChatService
def title
'Mattermost notifications'
end
def description
'Receive event notifications in Mattermost'
end
def to_param
'mattermost'
end
def help
'This service sends notifications about projects events to Mattermost channels.<br />
To set up this service:
<ol>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li>
<li>Paste the webhook <strong>URL</strong> into the field bellow. </li>
<li>Select events below to enable notifications. The channel and username are optional. </li>
</ol>'
end
def fields
default_fields + build_event_channels
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel
"#town-square"
end
end
class MattermostSlashCommandsService < ChatService class MattermostSlashCommandsService < Service
include TriggersHelper include TriggersHelper
prop_accessor :token prop_accessor :token
def valid_token?(token)
self.respond_to?(:token) &&
self.token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
def supported_events
[]
end
def can_test? def can_test?
false false
end end
......
class SlackService < Service class SlackService < ChatService
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
# Custom serialized properties initialization
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
self.notify_only_broken_pipelines = true
end
end
def title def title
'Slack' 'Slack notifications'
end end
def description def description
'A team communication tool for the 21st century' 'Receive event notifications in Slack'
end end
def to_param def to_param
...@@ -27,150 +12,29 @@ class SlackService < Service ...@@ -27,150 +12,29 @@ class SlackService < Service
end end
def help def help
'This service sends notifications to your Slack channel.<br/> 'This service sends notifications about projects events to Slack channels.<br />
To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel, To setup this service:
and enter the Webhook URL below.' <ol>
<li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li>
<li>Paste the <strong>Webhook URL</strong> into the field below. </li>
<li>Select events below to enable notifications. The channel and username are optional. </li>
</ol>'
end end
def fields def fields
default_fields =
[
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'channel', placeholder: "#general" },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
default_fields + build_event_channels default_fields + build_event_channels
end end
def supported_events def default_fields
%w[push issue confidential_issue merge_request note tag_push [
build pipeline wiki_page] { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
end { type: 'text', name: 'username', placeholder: 'username' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
def execute(data) { type: 'checkbox', name: 'notify_only_broken_pipelines' },
return unless supported_events.include?(data[:object_kind]) ]
return unless webhook.present?
object_kind = data[:object_kind]
data = data.merge(
project_url: project_url,
project_name: project_name
)
# WebHook events often have an 'update' event that follows a 'open' or
# 'close' action. Ignore update events for now to prevent duplicate
# messages from arriving.
message = get_message(object_kind, data)
if message
opt = {}
event_channel = get_channel_field(object_kind) || channel
opt[:channel] = event_channel if event_channel
opt[:username] = username if username
notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
true
else
false
end
end
def event_channel_names
supported_events.map { |event| event_channel_name(event) }
end
def event_field(event)
fields.find { |field| field[:name] == event_channel_name(event) }
end end
def global_fields def default_channel
fields.reject { |field| field[:name].end_with?('channel') } "#general"
end
private
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
PushMessage.new(data)
when "issue"
IssueMessage.new(data) unless is_update?(data)
when "merge_request"
MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
WikiPageMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name)
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
end
end
def event_channel_name(event)
"#{event}_channel"
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
def project_url
project.web_url
end
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end end
end end
require "slack_service/issue_message"
require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
require "slack_service/build_message"
require "slack_service/pipeline_message"
require "slack_service/wiki_page_message"
...@@ -220,6 +220,7 @@ class Service < ActiveRecord::Base ...@@ -220,6 +220,7 @@ class Service < ActiveRecord::Base
pivotaltracker pivotaltracker
pushover pushover
redmine redmine
mattermost
slack slack
teamcity teamcity
] ]
......
---
title: Create mattermost service
merge_request:
author:
...@@ -703,9 +703,9 @@ Get Redmine service settings for a project. ...@@ -703,9 +703,9 @@ Get Redmine service settings for a project.
GET /projects/:id/services/redmine GET /projects/:id/services/redmine
``` ```
## Slack ## Slack notifications
A team communication tool for the 21st century Receive event notifications in Slack
### Create/Edit Slack service ### Create/Edit Slack service
...@@ -737,6 +737,40 @@ Get Slack service settings for a project. ...@@ -737,6 +737,40 @@ Get Slack service settings for a project.
GET /projects/:id/services/slack GET /projects/:id/services/slack
``` ```
## Mattermost notifications
Receive event notifications in Mattermost
### Create/Edit Mattermost notifications service
Set Mattermost service for a project.
```
PUT /projects/:id/services/mattermost
```
Parameters:
- `webhook` (**required**) - https://mattermost.example/hooks/1298aff...
- `username` (optional) - username
- `channel` (optional) - #channel
### Delete Mattermost notifications service
Delete Mattermost Notifications service for a project.
```
DELETE /projects/:id/services/mattermost
```
### Get Mattermost notifications service settings
Get Mattermost notifications service settings for a project.
```
GET /projects/:id/services/mattermost
```
## JetBrains TeamCity CI ## JetBrains TeamCity CI
A continuous integration and build server A continuous integration and build server
......
# Mattermost Notifications Service
## On Mattermost
To enable Mattermost integration you must create an incoming webhook integration:
1. Sign in to your Mattermost instance
1. Visit incoming webhooks, that will be something like: https://mattermost.example/your_team_name/integrations/incoming_webhooks/add
1. Choose a display name, description and channel, those can be overridden on GitLab
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
it on https://mattermost.example/admin_console/integrations/custom.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
## On GitLab
After you set up Mattermost, it's time to set up GitLab.
Go to your project's **Settings > Services > Mattermost Notifications** and you will see a
checkbox with the following events that can be triggered:
- Push
- Issue
- Merge request
- Note
- Tag push
- Build
- Wiki page
Bellow each of these event checkboxes, you will have an input field to insert
which Mattermost channel you want to send that event message, with `#town-square`
being the default. The hash sign is optional.
At the end, fill in your Mattermost details:
| Field | Description |
| ----- | ----------- |
| **Webhook** | The incoming webhooks which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo... |
| **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. |
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
![Mattermost configuration](img/mattermost_configuration.png)
...@@ -44,10 +44,11 @@ further configuration instructions and details. Contributions are welcome. ...@@ -44,10 +44,11 @@ further configuration instructions and details. Contributions are welcome.
| JetBrains TeamCity CI | A continuous integration and build server | | JetBrains TeamCity CI | A continuous integration and build server |
| [Kubernetes](kubernetes.md) | A containerized deployment service | | [Kubernetes](kubernetes.md) | A containerized deployment service |
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands | | [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
| [Slack Notifications](slack.md) | Receive event notifications in Slack |
| PivotalTracker | Project Management Software (Source Commits Endpoint) | | PivotalTracker | Project Management Software (Source Commits Endpoint) |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop | | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker | | [Redmine](redmine.md) | Redmine issue tracker |
| [Slack](slack.md) | A team communication tool for the 21st century |
## Services Templates ## Services Templates
......
# Slack Service # Slack Notifications Service
## On Slack ## On Slack
...@@ -15,7 +15,7 @@ Slack: ...@@ -15,7 +15,7 @@ Slack:
After you set up Slack, it's time to set up GitLab. After you set up Slack, it's time to set up GitLab.
Go to your project's **Settings > Services > Slack** and you will see a Go to your project's **Settings > Services > Slack Notifications** and you will see a
checkbox with the following events that can be triggered: checkbox with the following events that can be triggered:
- Push - Push
......
...@@ -137,6 +137,7 @@ project: ...@@ -137,6 +137,7 @@ project:
- asana_service - asana_service
- gemnasium_service - gemnasium_service
- slack_service - slack_service
- mattermost_service
- buildkite_service - buildkite_service
- bamboo_service - bamboo_service
- teamcity_service - teamcity_service
......
require 'spec_helper' require 'spec_helper'
describe SlackService::BuildMessage do describe ChatMessage::BuildMessage do
subject { SlackService::BuildMessage.new(args) } subject { described_class.new(args) }
let(:args) do let(:args) do
{ {
......
require 'spec_helper' require 'spec_helper'
describe SlackService::IssueMessage, models: true do describe ChatMessage::IssueMessage, models: true do
subject { SlackService::IssueMessage.new(args) } subject { described_class.new(args) }
let(:args) do let(:args) do
{ {
......
require 'spec_helper' require 'spec_helper'
describe SlackService::MergeMessage, models: true do describe ChatMessage::MergeMessage, models: true do
subject { SlackService::MergeMessage.new(args) } subject { described_class.new(args) }
let(:args) do let(:args) do
{ {
......
require 'spec_helper' require 'spec_helper'
describe SlackService::NoteMessage, models: true do describe ChatMessage::NoteMessage, models: true do
let(:color) { '#345' } let(:color) { '#345' }
before do before do
...@@ -36,7 +36,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -36,7 +36,7 @@ describe SlackService::NoteMessage, models: true do
end end
it 'returns a message regarding notes on commits' do it 'returns a message regarding notes on commits' do
message = SlackService::NoteMessage.new(@args) message = described_class.new(@args)
expect(message.pretext).to eq("test.user <url|commented on " \ expect(message.pretext).to eq("test.user <url|commented on " \
"commit 5f163b2b> in <somewhere.com|project_name>: " \ "commit 5f163b2b> in <somewhere.com|project_name>: " \
"*Added a commit message*") "*Added a commit message*")
...@@ -62,7 +62,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -62,7 +62,7 @@ describe SlackService::NoteMessage, models: true do
end end
it 'returns a message regarding notes on a merge request' do it 'returns a message regarding notes on a merge request' do
message = SlackService::NoteMessage.new(@args) message = described_class.new(@args)
expect(message.pretext).to eq("test.user <url|commented on " \ expect(message.pretext).to eq("test.user <url|commented on " \
"merge request !30> in <somewhere.com|project_name>: " \ "merge request !30> in <somewhere.com|project_name>: " \
"*merge request title*") "*merge request title*")
...@@ -88,7 +88,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -88,7 +88,7 @@ describe SlackService::NoteMessage, models: true do
end end
it 'returns a message regarding notes on an issue' do it 'returns a message regarding notes on an issue' do
message = SlackService::NoteMessage.new(@args) message = described_class.new(@args)
expect(message.pretext).to eq( expect(message.pretext).to eq(
"test.user <url|commented on " \ "test.user <url|commented on " \
"issue #20> in <somewhere.com|project_name>: " \ "issue #20> in <somewhere.com|project_name>: " \
...@@ -114,7 +114,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -114,7 +114,7 @@ describe SlackService::NoteMessage, models: true do
end end
it 'returns a message regarding notes on a project snippet' do it 'returns a message regarding notes on a project snippet' do
message = SlackService::NoteMessage.new(@args) message = described_class.new(@args)
expect(message.pretext).to eq("test.user <url|commented on " \ expect(message.pretext).to eq("test.user <url|commented on " \
"snippet #5> in <somewhere.com|project_name>: " \ "snippet #5> in <somewhere.com|project_name>: " \
"*snippet title*") "*snippet title*")
......
require 'spec_helper' require 'spec_helper'
describe SlackService::PipelineMessage do describe ChatMessage::PipelineMessage do
subject { SlackService::PipelineMessage.new(args) } subject { described_class.new(args) }
let(:user) { { name: 'hacker' } } let(:user) { { name: 'hacker' } }
let(:args) do let(:args) do
......
require 'spec_helper' require 'spec_helper'
describe SlackService::PushMessage, models: true do describe ChatMessage::PushMessage, models: true do
subject { SlackService::PushMessage.new(args) } subject { described_class.new(args) }
let(:args) do let(:args) do
{ {
......
require 'spec_helper' require 'spec_helper'
describe SlackService::WikiPageMessage, models: true do describe ChatMessage::WikiPageMessage, models: true do
subject { described_class.new(args) } subject { described_class.new(args) }
let(:args) do let(:args) do
......
...@@ -2,14 +2,7 @@ require 'spec_helper' ...@@ -2,14 +2,7 @@ require 'spec_helper'
describe ChatService, models: true do describe ChatService, models: true do
describe "Associations" do describe "Associations" do
it { is_expected.to have_many :chat_names } before { allow(subject).to receive(:activated?).and_return(true) }
end it { is_expected.to validate_presence_of :webhook }
describe '#valid_token?' do
subject { described_class.new }
it 'is false as it has no token' do
expect(subject.valid_token?('wer')).to be_falsey
end
end end
end end
require 'spec_helper'
describe MattermostService, models: true do
it_behaves_like "slack or mattermost"
end
require 'spec_helper' require 'spec_helper'
describe SlackService, models: true do describe SlackService, models: true do
let(:slack) { SlackService.new } it_behaves_like "slack or mattermost"
let(:webhook_url) { 'https://example.gitlab.com/' }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:webhook) }
it_behaves_like 'issue tracker service URL attribute', :webhook
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:webhook) }
end
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:username) { 'slack_username' }
let(:channel) { 'slack_channel' }
let(:push_sample_data) do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
before do
allow(slack).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
opts = {
title: 'Awesome issue',
description: 'please fix'
}
issue_service = Issues::CreateService.new(project, user, opts)
@issue = issue_service.execute
@issues_sample_data = issue_service.hook_data(@issue, 'open')
opts = {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'feature',
target_branch: 'master'
}
merge_service = MergeRequests::CreateService.new(project,
user, opts)
@merge_request = merge_service.execute
@merge_sample_data = merge_service.hook_data(@merge_request,
'open')
opts = {
title: "Awesome wiki_page",
content: "Some text describing some thing or another",
format: "md",
message: "user created page: Awesome wiki_page"
}
wiki_page_service = WikiPages::CreateService.new(project, user, opts)
@wiki_page = wiki_page_service.execute
@wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
end
it "calls Slack API for push events" do
slack.execute(push_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack API for issue events" do
slack.execute(@issues_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack API for merge requests events" do
slack.execute(@merge_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack API for wiki page events" do
slack.execute(@wiki_page_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it 'uses the username as an option for slack when configured' do
allow(slack).to receive(:username).and_return(username)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, username: username).
and_return(
double(:slack_service).as_null_object
)
slack.execute(push_sample_data)
end
it 'uses the channel as an option when it is configured' do
allow(slack).to receive(:channel).and_return(channel)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: channel).
and_return(
double(:slack_service).as_null_object
)
slack.execute(push_sample_data)
end
context "event channels" do
it "uses the right channel for push event" do
slack.update_attributes(push_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(push_sample_data)
end
it "uses the right channel for merge request event" do
slack.update_attributes(merge_request_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@merge_sample_data)
end
it "uses the right channel for issue event" do
slack.update_attributes(issue_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@issues_sample_data)
end
it "uses the right channel for wiki event" do
slack.update_attributes(wiki_page_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@wiki_page_sample_data)
end
context "note event" do
let(:issue_note) do
create(:note_on_issue, project: project, note: "issue note")
end
it "uses the right channel" do
slack.update_attributes(note_channel: "random")
note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(note_data)
end
end
end
end
describe "Note events" do
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id) }
before do
allow(slack).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
end
context 'when commit comment event executed' do
let(:commit_note) do
create(:note_on_commit, author: user,
project: project,
commit_id: project.repository.commit.id,
note: 'a comment on a commit')
end
it "calls Slack API for commit comment events" do
data = Gitlab::DataBuilder::Note.build(commit_note, user)
slack.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when merge request comment event executed' do
let(:merge_request_note) do
create(:note_on_merge_request, project: project,
note: "merge request note")
end
it "calls Slack API for merge request comment events" do
data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
slack.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when issue comment event executed' do
let(:issue_note) do
create(:note_on_issue, project: project, note: "issue note")
end
it "calls Slack API for issue comment events" do
data = Gitlab::DataBuilder::Note.build(issue_note, user)
slack.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when snippet comment event executed' do
let(:snippet_note) do
create(:note_on_project_snippet, project: project,
note: "snippet note")
end
it "calls Slack API for snippet comment events" do
data = Gitlab::DataBuilder::Note.build(snippet_note, user)
slack.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
end
describe 'Pipeline events' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
sha: project.commit.sha, ref: project.default_branch)
end
before do
allow(slack).to receive_messages(
project: project,
service_hook: true,
webhook: webhook_url
)
end
shared_examples 'call Slack API' do
before do
WebMock.stub_request(:post, webhook_url)
end
it 'calls Slack API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
slack.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'with failed pipeline' do
let(:status) { 'failed' }
it_behaves_like 'call Slack API'
end
context 'with succeeded pipeline' do
let(:status) { 'success' }
context 'with default to notify_only_broken_pipelines' do
it 'does not call Slack API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
result = slack.execute(data)
expect(result).to be_falsy
end
end
context 'with setting notify_only_broken_pipelines to false' do
before do
slack.notify_only_broken_pipelines = false
end
it_behaves_like 'call Slack API'
end
end
end
end end
...@@ -23,6 +23,7 @@ describe Project, models: true do ...@@ -23,6 +23,7 @@ describe Project, models: true do
it { is_expected.to have_many(:chat_services) } it { is_expected.to have_many(:chat_services) }
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:slack_service).dependent(:destroy) }
it { is_expected.to have_one(:mattermost_service).dependent(:destroy) }
it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:boards).dependent(:destroy) } it { is_expected.to have_many(:boards).dependent(:destroy) }
......
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
RSpec.shared_examples 'slack or mattermost' do
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:webhook) }
it_behaves_like 'issue tracker service URL attribute', :webhook
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:webhook) }
end
end
describe "#execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:username) { 'slack_username' }
let(:channel) { 'slack_channel' }
let(:push_sample_data) do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
before do
allow(chat_service).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
opts = {
title: 'Awesome issue',
description: 'please fix'
}
issue_service = Issues::CreateService.new(project, user, opts)
@issue = issue_service.execute
@issues_sample_data = issue_service.hook_data(@issue, 'open')
opts = {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'feature',
target_branch: 'master'
}
merge_service = MergeRequests::CreateService.new(project,
user, opts)
@merge_request = merge_service.execute
@merge_sample_data = merge_service.hook_data(@merge_request,
'open')
opts = {
title: "Awesome wiki_page",
content: "Some text describing some thing or another",
format: "md",
message: "user created page: Awesome wiki_page"
}
wiki_page_service = WikiPages::CreateService.new(project, user, opts)
@wiki_page = wiki_page_service.execute
@wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
end
it "calls Slack/Mattermost API for push events" do
chat_service.execute(push_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack/Mattermost API for issue events" do
chat_service.execute(@issues_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack/Mattermost API for merge requests events" do
chat_service.execute(@merge_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it "calls Slack/Mattermost API for wiki page events" do
chat_service.execute(@wiki_page_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
it 'uses the username as an option for slack when configured' do
allow(chat_service).to receive(:username).and_return(username)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, username: username, channel: chat_service.default_channel).
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(push_sample_data)
end
it 'uses the channel as an option when it is configured' do
allow(chat_service).to receive(:channel).and_return(channel)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: channel).
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(push_sample_data)
end
context "event channels" do
it "uses the right channel for push event" do
chat_service.update_attributes(push_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(push_sample_data)
end
it "uses the right channel for merge request event" do
chat_service.update_attributes(merge_request_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(@merge_sample_data)
end
it "uses the right channel for issue event" do
chat_service.update_attributes(issue_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(@issues_sample_data)
end
it "uses the right channel for wiki event" do
chat_service.update_attributes(wiki_page_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(@wiki_page_sample_data)
end
context "note event" do
let(:issue_note) do
create(:note_on_issue, project: project, note: "issue note")
end
it "uses the right channel" do
chat_service.update_attributes(note_channel: "random")
note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
chat_service.execute(note_data)
end
end
end
end
describe "Note events" do
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id) }
before do
allow(chat_service).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
end
context 'when commit comment event executed' do
let(:commit_note) do
create(:note_on_commit, author: user,
project: project,
commit_id: project.repository.commit.id,
note: 'a comment on a commit')
end
it "calls Slack/Mattermost API for commit comment events" do
data = Gitlab::DataBuilder::Note.build(commit_note, user)
chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when merge request comment event executed' do
let(:merge_request_note) do
create(:note_on_merge_request, project: project,
note: "merge request note")
end
it "calls Slack API for merge request comment events" do
data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when issue comment event executed' do
let(:issue_note) do
create(:note_on_issue, project: project, note: "issue note")
end
it "calls Slack API for issue comment events" do
data = Gitlab::DataBuilder::Note.build(issue_note, user)
chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'when snippet comment event executed' do
let(:snippet_note) do
create(:note_on_project_snippet, project: project,
note: "snippet note")
end
it "calls Slack API for snippet comment events" do
data = Gitlab::DataBuilder::Note.build(snippet_note, user)
chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
end
describe 'Pipeline events' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
sha: project.commit.sha, ref: project.default_branch)
end
before do
allow(chat_service).to receive_messages(
project: project,
service_hook: true,
webhook: webhook_url
)
end
shared_examples 'call Slack/Mattermost API' do
before do
WebMock.stub_request(:post, webhook_url)
end
it 'calls Slack/Mattermost API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
context 'with failed pipeline' do
let(:status) { 'failed' }
it_behaves_like 'call Slack/Mattermost API'
end
context 'with succeeded pipeline' do
let(:status) { 'success' }
context 'with default to notify_only_broken_pipelines' do
it 'does not call Slack/Mattermost API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
result = chat_service.execute(data)
expect(result).to be_falsy
end
end
context 'with setting notify_only_broken_pipelines to false' do
before do
chat_service.notify_only_broken_pipelines = false
end
it_behaves_like 'call Slack/Mattermost API'
end
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