diff options
author | Felipe Artur <felipefac@gmail.com> | 2016-11-25 17:36:37 -0200 |
---|---|---|
committer | Felipe Artur <felipefac@gmail.com> | 2016-12-15 11:09:31 -0200 |
commit | 141faaacf9119ce5d765efe73c6509030ba078cd (patch) | |
tree | 1f5cf350087ad524273e0ba80c7ddcc3ce640ad8 | |
parent | 2d1dfae9b63d35232e4bf537a0aca9b95e2d5e72 (diff) | |
download | gitlab-ce-141faaacf9119ce5d765efe73c6509030ba078cd.tar.gz |
Mattermost Notifications Service
33 files changed, 658 insertions, 524 deletions
diff --git a/app/models/project.rb b/app/models/project.rb index 2c726cfc5df..19c2d24212d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -95,6 +95,7 @@ class Project < ActiveRecord::Base has_one :asana_service, dependent: :destroy has_one :gemnasium_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 :buildkite_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/chat_message/base_message.rb index f1182824687..a03605d01fb 100644 --- a/app/models/project_services/slack_service/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -1,6 +1,6 @@ require 'slack-notifier' -class SlackService +module ChatMessage class BaseMessage def initialize(params) raise NotImplementedError diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/chat_message/build_message.rb index 0fca4267bad..53e35cb21bf 100644 --- a/app/models/project_services/slack_service/build_message.rb +++ b/app/models/project_services/chat_message/build_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class BuildMessage < BaseMessage attr_reader :sha attr_reader :ref_type diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb index cd87a79d0c6..14fd64e5332 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/chat_message/issue_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class IssueMessage < BaseMessage attr_reader :user_name attr_reader :title diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb index b7615c96068..ab5e8b24167 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/chat_message/merge_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class MergeMessage < BaseMessage attr_reader :user_name attr_reader :project_name diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/chat_message/note_message.rb index 797c5937f09..ca1d7207034 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/chat_message/note_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class NoteMessage < BaseMessage attr_reader :message attr_reader :user_name diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb index b6355fc4171..210027565a8 100644 --- a/app/models/project_services/slack_service/pipeline_message.rb +++ b/app/models/project_services/chat_message/pipeline_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class PipelineMessage < BaseMessage attr_reader :ref_type, :ref, :status, :project_name, :project_url, :user_name, :duration, :pipeline_id diff --git a/app/models/project_services/slack_service/push_message.rb b/app/models/project_services/chat_message/push_message.rb index b26f3e9ddce..2d73b71ec37 100644 --- a/app/models/project_services/slack_service/push_message.rb +++ b/app/models/project_services/chat_message/push_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class PushMessage < BaseMessage attr_reader :after attr_reader :before diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb index 160ca3ac115..134083e4504 100644 --- a/app/models/project_services/slack_service/wiki_page_message.rb +++ b/app/models/project_services/chat_message/wiki_page_message.rb @@ -1,4 +1,4 @@ -class SlackService +module ChatMessage class WikiPageMessage < BaseMessage attr_reader :user_name attr_reader :title diff --git a/app/models/project_services/chat_service.rb b/app/models/project_services/chat_service.rb index d36beff5fa6..8ac049ba939 100644 --- a/app/models/project_services/chat_service.rb +++ b/app/models/project_services/chat_service.rb @@ -1,21 +1,148 @@ # Base class for Chat services # This class is not meant to be used directly, but only to inherrit from. class ChatService < Service + include ChatMessage + 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) - self.respond_to?(:token) && - self.token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + if properties.nil? + self.properties = {} + self.notify_only_broken_builds = true + self.notify_only_broken_pipelines = true + end + end + + def can_test? + valid? end def supported_events - [] + %w[push issue confidential_issue merge_request note tag_push + build pipeline wiki_page] 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 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 diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb new file mode 100644 index 00000000000..9d61c251a32 --- /dev/null +++ b/app/models/project_services/mattermost_service.rb @@ -0,0 +1,41 @@ +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 diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 33431f41dc2..3993dfbda17 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -1,8 +1,18 @@ -class MattermostSlashCommandsService < ChatService +class MattermostSlashCommandsService < Service include TriggersHelper 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? false end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index e1b937817f4..0df1743c4ba 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -1,25 +1,10 @@ -class SlackService < Service - 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 - +class SlackService < ChatService def title - 'Slack' + 'Slack notifications' end def description - 'A team communication tool for the 21st century' + 'Receive event notifications in Slack' end def to_param @@ -27,150 +12,29 @@ class SlackService < Service end def help - 'This service sends notifications to your Slack channel.<br/> - To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel, - and enter the Webhook URL below.' + 'This service sends notifications about projects events to Slack channels.<br /> + To setup this service: + <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 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 end - def supported_events - %w[push issue confidential_issue merge_request note tag_push - build pipeline wiki_page] - end - - 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) - - 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) } + def default_fields + [ + { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }, + { type: 'text', name: 'username', placeholder: 'username' }, + { type: 'checkbox', name: 'notify_only_broken_builds' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' }, + ] end - def global_fields - fields.reject { |field| field[:name].end_with?('channel') } - 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 + def default_channel + "#general" 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" diff --git a/app/models/service.rb b/app/models/service.rb index e49a8fa2904..8e58f2a1925 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -220,6 +220,7 @@ class Service < ActiveRecord::Base pivotaltracker pushover redmine + mattermost slack teamcity ] diff --git a/changelogs/unreleased/issue_22269.yml b/changelogs/unreleased/issue_22269.yml new file mode 100644 index 00000000000..6b7164aff77 --- /dev/null +++ b/changelogs/unreleased/issue_22269.yml @@ -0,0 +1,4 @@ +--- +title: Create mattermost service +merge_request: +author: diff --git a/doc/api/services.md b/doc/api/services.md index 3dad953cd1e..1466b8189b0 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -703,9 +703,9 @@ Get Redmine service settings for a project. 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 @@ -737,6 +737,40 @@ Get Slack service settings for a project. 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 A continuous integration and build server diff --git a/doc/project_services/img/mattermost_configuration.png b/doc/project_services/img/mattermost_configuration.png Binary files differnew file mode 100644 index 00000000000..3c5ff5ee317 --- /dev/null +++ b/doc/project_services/img/mattermost_configuration.png diff --git a/doc/project_services/mattermost.md b/doc/project_services/mattermost.md new file mode 100644 index 00000000000..fbc7dfeee6d --- /dev/null +++ b/doc/project_services/mattermost.md @@ -0,0 +1,45 @@ +# 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) diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index a7bcd186a8c..0f398874b8f 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -44,10 +44,11 @@ further configuration instructions and details. Contributions are welcome. | JetBrains TeamCity CI | A continuous integration and build server | | [Kubernetes](kubernetes.md) | A containerized deployment service | | [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) | | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop | | [Redmine](redmine.md) | Redmine issue tracker | -| [Slack](slack.md) | A team communication tool for the 21st century | ## Services Templates diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md index 3cfe77c9f85..0b682b43810 100644 --- a/doc/project_services/slack.md +++ b/doc/project_services/slack.md @@ -1,4 +1,4 @@ -# Slack Service +# Slack Notifications Service ## On Slack @@ -15,7 +15,7 @@ Slack: 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: - Push diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index c4ee838b7c9..068137f6255 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -137,6 +137,7 @@ project: - asana_service - gemnasium_service - slack_service +- mattermost_service - buildkite_service - bamboo_service - teamcity_service diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb index 452f4e2782c..b71d153f814 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/chat_message/build_message_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackService::BuildMessage do - subject { SlackService::BuildMessage.new(args) } +describe ChatMessage::BuildMessage do + subject { described_class.new(args) } let(:args) do { diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index 98c36ec088d..ebe0ead4408 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackService::IssueMessage, models: true do - subject { SlackService::IssueMessage.new(args) } +describe ChatMessage::IssueMessage, models: true do + subject { described_class.new(args) } let(:args) do { diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index c5c052d9af1..07c414c6ca4 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackService::MergeMessage, models: true do - subject { SlackService::MergeMessage.new(args) } +describe ChatMessage::MergeMessage, models: true do + subject { described_class.new(args) } let(:args) do { diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 97f818125d3..31936da40a2 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::NoteMessage, models: true do +describe ChatMessage::NoteMessage, models: true do let(:color) { '#345' } before do @@ -36,7 +36,7 @@ describe SlackService::NoteMessage, models: true do end 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 " \ "commit 5f163b2b> in <somewhere.com|project_name>: " \ "*Added a commit message*") @@ -62,7 +62,7 @@ describe SlackService::NoteMessage, models: true do end 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 " \ "merge request !30> in <somewhere.com|project_name>: " \ "*merge request title*") @@ -88,7 +88,7 @@ describe SlackService::NoteMessage, models: true do end 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( "test.user <url|commented on " \ "issue #20> in <somewhere.com|project_name>: " \ @@ -114,7 +114,7 @@ describe SlackService::NoteMessage, models: true do end 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 " \ "snippet #5> in <somewhere.com|project_name>: " \ "*snippet title*") diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index 4098500122f..eca71db07b6 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackService::PipelineMessage do - subject { SlackService::PipelineMessage.new(args) } +describe ChatMessage::PipelineMessage do + subject { described_class.new(args) } let(:user) { { name: 'hacker' } } let(:args) do diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 17cd05e24f1..b781c4505db 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackService::PushMessage, models: true do - subject { SlackService::PushMessage.new(args) } +describe ChatMessage::PushMessage, models: true do + subject { described_class.new(args) } let(:args) do { diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 093911598b0..94c04dc0865 100644 --- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::WikiPageMessage, models: true do +describe ChatMessage::WikiPageMessage, models: true do subject { described_class.new(args) } let(:args) do diff --git a/spec/models/project_services/chat_service_spec.rb b/spec/models/project_services/chat_service_spec.rb index c6a45a3e1be..e6314a43501 100644 --- a/spec/models/project_services/chat_service_spec.rb +++ b/spec/models/project_services/chat_service_spec.rb @@ -2,14 +2,7 @@ require 'spec_helper' describe ChatService, models: true do describe "Associations" do - it { is_expected.to have_many :chat_names } - end - - 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 + before { allow(subject).to receive(:activated?).and_return(true) } + it { is_expected.to validate_presence_of :webhook } end end diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb new file mode 100644 index 00000000000..1e5b4c715c3 --- /dev/null +++ b/spec/models/project_services/mattermost_service_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe MattermostService, models: true do + it_behaves_like "slack or mattermost" +end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index c07a70a8069..4928391fd7e 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -1,327 +1,5 @@ require 'spec_helper' describe SlackService, models: true do - let(:slack) { SlackService.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(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 + it_behaves_like "slack or mattermost" end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 21ff238841e..1d8e42202ea 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -23,6 +23,7 @@ describe Project, models: true do 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(: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(:asana_service).dependent(:destroy) } it { is_expected.to have_many(:boards).dependent(:destroy) } diff --git a/spec/support/slack_mattermost_shared_examples.rb b/spec/support/slack_mattermost_shared_examples.rb new file mode 100644 index 00000000000..56d4965f74d --- /dev/null +++ b/spec/support/slack_mattermost_shared_examples.rb @@ -0,0 +1,328 @@ +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 |