Commit c646f407 authored by Luke Duncalfe's avatar Luke Duncalfe

Move chat integrations to `Integrations::`

https://gitlab.com/gitlab-org/gitlab/-/issues/201855
parent c866f1f5
...@@ -1648,20 +1648,13 @@ Gitlab/NamespacedClass: ...@@ -1648,20 +1648,13 @@ Gitlab/NamespacedClass:
- 'app/models/project_services/alerts_service.rb' - 'app/models/project_services/alerts_service.rb'
- 'app/models/project_services/alerts_service_data.rb' - 'app/models/project_services/alerts_service_data.rb'
- 'app/models/project_services/chat_notification_service.rb' - 'app/models/project_services/chat_notification_service.rb'
- 'app/models/project_services/discord_service.rb'
- 'app/models/project_services/hangouts_chat_service.rb'
- 'app/models/project_services/mattermost_service.rb'
- 'app/models/project_services/mattermost_slash_commands_service.rb' - 'app/models/project_services/mattermost_slash_commands_service.rb'
- 'app/models/project_services/microsoft_teams_service.rb'
- 'app/models/project_services/mock_monitoring_service.rb' - 'app/models/project_services/mock_monitoring_service.rb'
- 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb'
- 'app/models/project_services/prometheus_service.rb' - 'app/models/project_services/prometheus_service.rb'
- 'app/models/project_services/pushover_service.rb' - 'app/models/project_services/pushover_service.rb'
- 'app/models/project_services/slack_service.rb'
- 'app/models/project_services/slack_slash_commands_service.rb' - 'app/models/project_services/slack_slash_commands_service.rb'
- 'app/models/project_services/slash_commands_service.rb' - 'app/models/project_services/slash_commands_service.rb'
- 'app/models/project_services/unify_circuit_service.rb'
- 'app/models/project_services/webex_teams_service.rb'
- 'app/models/project_setting.rb' - 'app/models/project_setting.rb'
- 'app/models/project_snippet.rb' - 'app/models/project_snippet.rb'
- 'app/models/project_statistics.rb' - 'app/models/project_statistics.rb'
......
...@@ -7,8 +7,8 @@ class ChatTeam < ApplicationRecord ...@@ -7,8 +7,8 @@ class ChatTeam < ApplicationRecord
belongs_to :namespace belongs_to :namespace
def remove_mattermost_team(current_user) def remove_mattermost_team(current_user)
Mattermost::Team.new(current_user).destroy(team_id: team_id) ::Mattermost::Team.new(current_user).destroy(team_id: team_id)
rescue Mattermost::ClientError => e rescue ::Mattermost::ClientError => e
# Either the group is not found, or the user doesn't have the proper # Either the group is not found, or the user doesn't have the proper
# access on the mattermost instance. In the first case, we're done either way # access on the mattermost instance. In the first case, we're done either way
# in the latter case, we can't recover by retrying, so we just log what happened # in the latter case, we can't recover by retrying, so we just log what happened
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Concern handling functionality around deciding whether to send notification # Concern handling functionality around deciding whether to send notification
# for activities on a specified branch or not. Will be included in # for activities on a specified branch or not. Will be included in
# ChatNotificationService and PipelinesEmailService classes. # Integrations::BaseChatNotification and PipelinesEmailService classes.
module NotificationBranchSelection module NotificationBranchSelection
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
module Integrations
class BaseChatNotification < Integration
include ChatMessage
include NotificationBranchSelection
SUPPORTED_EVENTS = %w[
push issue confidential_issue merge_request note confidential_note
tag_push pipeline wiki_page deployment
].freeze
SUPPORTED_EVENTS_FOR_LABEL_FILTER = %w[issue confidential_issue merge_request note confidential_note].freeze
EVENT_CHANNEL = proc { |event| "#{event}_channel" }
LABEL_NOTIFICATION_BEHAVIOURS = [
MATCH_ANY_LABEL = 'match_any',
MATCH_ALL_LABELS = 'match_all'
].freeze
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified, :labels_to_be_notified_behavior
# Custom serialized properties initialization
prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, public_url: true, if: :activated?
validates :labels_to_be_notified_behavior, inclusion: { in: LABEL_NOTIFICATION_BEHAVIOURS }, allow_blank: true
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
self.branches_to_be_notified = "default"
self.labels_to_be_notified_behavior = MATCH_ANY_LABEL
elsif !self.notify_only_default_branch.nil?
# In older versions, there was only a boolean property named
# `notify_only_default_branch`. Now we have a string property named
# `branches_to_be_notified`. Instead of doing a background migration, we
# opted to set a value for the new property based on the old one, if
# users haven't specified one already. When users edit the service and
# select a value for this new property, it will override everything.
self.branches_to_be_notified ||= notify_only_default_branch? ? "default" : "all"
end
end
def confidential_issue_channel
properties['confidential_issue_channel'].presence || properties['issue_channel']
end
def confidential_note_channel
properties['confidential_note_channel'].presence || properties['note_channel']
end
def self.supported_events
SUPPORTED_EVENTS
end
def fields
default_fields + build_event_channels
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}", required: true }.freeze,
{ type: 'text', name: 'username', placeholder: 'GitLab-integration' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'Do not send notifications for successful pipelines.' }.freeze,
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze,
{
type: 'text',
name: 'labels_to_be_notified',
placeholder: '~backend,~frontend',
help: 'Send notifications for issue, merge request, and comment events with the listed labels only. Leave blank to receive notifications for all events.'
}.freeze,
{
type: 'select',
name: 'labels_to_be_notified_behavior',
choices: [
['Match any of the labels', MATCH_ANY_LABEL],
['Match all of the labels', MATCH_ALL_LABELS]
]
}.freeze
].freeze
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
object_kind = data[:object_kind]
data = custom_data(data)
return unless notify_label?(data)
# 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
event_type = data[:event_type] || object_kind
channel_names = get_channel_field(event_type).presence || channel.presence
channels = channel_names&.split(',')&.map(&:strip)
opts = {}
opts[:channel] = channels if channels.present?
opts[:username] = username if username
if notify(message, opts)
log_usage(event_type, user_id_from_hook_data(data))
return true
end
false
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_placeholder
raise NotImplementedError
end
private
def log_usage(_, _)
# Implement in child class
end
def labels_to_be_notified_list
return [] if labels_to_be_notified.nil?
labels_to_be_notified.delete('~').split(',').map(&:strip)
end
def notify_label?(data)
return true unless SUPPORTED_EVENTS_FOR_LABEL_FILTER.include?(data[:object_kind]) && labels_to_be_notified.present?
labels = data[:labels] || data.dig(:issue, :labels) || data.dig(:merge_request, :labels) || data.dig(:object_attributes, :labels)
return false if labels.blank?
matching_labels = labels_to_be_notified_list & labels.pluck(:title)
if labels_to_be_notified_behavior == MATCH_ALL_LABELS
labels_to_be_notified_list.difference(matching_labels).empty?
else
matching_labels.any?
end
end
def user_id_from_hook_data(data)
data.dig(:user, :id) || data[:user_id]
end
# every notifier must implement this independently
def notify(message, opts)
raise NotImplementedError
end
def custom_data(data)
data.merge(project_url: project_url, project_name: project_name).with_indifferent_access
end
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
Integrations::ChatMessage::PushMessage.new(data) if notify_for_ref?(data)
when "issue"
Integrations::ChatMessage::IssueMessage.new(data) unless update?(data)
when "merge_request"
Integrations::ChatMessage::MergeMessage.new(data) unless update?(data)
when "note"
Integrations::ChatMessage::NoteMessage.new(data)
when "pipeline"
Integrations::ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
Integrations::ChatMessage::WikiPageMessage.new(data)
when "deployment"
Integrations::ChatMessage::DeploymentMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name) # rubocop:disable GitlabSecurity/PublicSend
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel_placeholder }
end
end
def event_channel_name(event)
EVENT_CHANNEL[event]
end
def project_name
project.full_name
end
def project_url
project.web_url
end
def update?(data)
data[:object_attributes][:action] == 'update'
end
def should_pipeline_be_notified?(data)
notify_for_ref?(data) && notify_for_pipeline?(data)
end
def notify_for_ref?(data)
return true if data[:object_kind] == 'tag_push'
return true if data.dig(:object_attributes, :tag)
notify_for_branch?(data)
end
def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end
end
end
...@@ -58,7 +58,7 @@ module Integrations ...@@ -58,7 +58,7 @@ module Integrations
end end
def format(string) def format(string)
Slack::Messenger::Util::LinkFormatter.format(format_relative_links(string)) ::Slack::Messenger::Util::LinkFormatter.format(format_relative_links(string))
end end
def format_relative_links(string) def format_relative_links(string)
......
...@@ -105,7 +105,7 @@ module Integrations ...@@ -105,7 +105,7 @@ module Integrations
def failed_stages_field def failed_stages_field
{ {
title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length), title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length),
value: Slack::Messenger::Util::LinkFormatter.format(failed_stages_links), value: ::Slack::Messenger::Util::LinkFormatter.format(failed_stages_links),
short: true short: true
} }
end end
...@@ -113,7 +113,7 @@ module Integrations ...@@ -113,7 +113,7 @@ module Integrations
def failed_jobs_field def failed_jobs_field
{ {
title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length), title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length),
value: Slack::Messenger::Util::LinkFormatter.format(failed_jobs_links), value: ::Slack::Messenger::Util::LinkFormatter.format(failed_jobs_links),
short: true short: true
} }
end end
...@@ -130,12 +130,12 @@ module Integrations ...@@ -130,12 +130,12 @@ module Integrations
fields = [ fields = [
{ {
title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"), title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"),
value: Slack::Messenger::Util::LinkFormatter.format(ref_link), value: ::Slack::Messenger::Util::LinkFormatter.format(ref_link),
short: true short: true
}, },
{ {
title: s_("ChatMessage|Commit"), title: s_("ChatMessage|Commit"),
value: Slack::Messenger::Util::LinkFormatter.format(commit_link), value: ::Slack::Messenger::Util::LinkFormatter.format(commit_link),
short: true short: true
} }
] ]
......
...@@ -49,7 +49,7 @@ module Integrations ...@@ -49,7 +49,7 @@ module Integrations
end end
def format(string) def format(string)
Slack::Messenger::Util::LinkFormatter.format(string) ::Slack::Messenger::Util::LinkFormatter.format(string)
end end
def commit_messages def commit_messages
......
# frozen_string_literal: true
require "discordrb/webhooks"
module Integrations
class Discord < BaseChatNotification
include ActionView::Helpers::UrlHelper
ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/.freeze
def title
s_("DiscordService|Discord Notifications")
end
def description
s_("DiscordService|Send notifications about project events to a Discord channel.")
end
def self.to_param
"discord"
end
def help
docs_link = link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/discord_notifications'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to a Discord channel. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def event_field(event)
# No-op.
end
def default_channel_placeholder
# No-op.
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page]
end
def default_fields
[
{ type: "text", name: "webhook", placeholder: "https://discordapp.com/api/webhooks/…", help: "URL to the webhook for the Discord channel." },
{ type: "checkbox", name: "notify_only_broken_pipelines" },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
client = Discordrb::Webhooks::Client.new(url: webhook)
client.execute do |builder|
builder.add_embed do |embed|
embed.author = Discordrb::Webhooks::EmbedAuthor.new(name: message.user_name, icon_url: message.user_avatar)
embed.description = (message.pretext + "\n" + Array.wrap(message.attachments).join("\n")).gsub(ATTACHMENT_REGEX, " \\k<entry> - \\k<name>\n")
end
end
rescue RestClient::Exception => error
log_error(error.message)
false
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
end
# frozen_string_literal: true
require 'hangouts_chat'
module Integrations
class HangoutsChat < BaseChatNotification
include ActionView::Helpers::UrlHelper
def title
'Google Chat'
end
def description
'Send notifications from GitLab to a room in Google Chat.'
end
def self.to_param
'hangouts_chat'
end
def help
docs_link = link_to _('How do I set up a Google Chat webhook?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/hangouts_chat'), target: '_blank', rel: 'noopener noreferrer'
s_('Before enabling this integration, create a webhook for the room in Google Chat where you want to receive notifications from this project. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def event_field(event)
end
def default_channel_placeholder
end
def webhook_placeholder
'https://chat.googleapis.com/v1/spaces…'
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
simple_text = parse_simple_text_message(message)
::HangoutsChat::Sender.new(webhook).simple(simple_text)
end
def parse_simple_text_message(message)
header = message.pretext
return header if message.attachments.empty?
attachment = message.attachments.first
title = format_attachment_title(attachment)
body = attachment[:text]
[header, title, body].compact.join("\n")
end
def format_attachment_title(attachment)
return attachment[:title] unless attachment[:title_link]
"<#{attachment[:title_link]}|#{attachment[:title]}>"
end
end
end
# frozen_string_literal: true
module Integrations
class Mattermost < BaseChatNotification
include SlackMattermost::Notifier
include ActionView::Helpers::UrlHelper
def title
s_('Mattermost notifications')
end
def description
s_('Send notifications about project events to Mattermost channels.')
end
def self.to_param
'mattermost'
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/mattermost'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to Mattermost channels. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def default_channel_placeholder
'my-channel'
end
def webhook_placeholder
'http://mattermost.example.com/hooks/'
end
end
end
# frozen_string_literal: true
module Integrations
class MicrosoftTeams < BaseChatNotification
def title
'Microsoft Teams notifications'
end
def description
'Send notifications about project events to Microsoft Teams.'
end
def self.to_param
'microsoft_teams'
end
def help
'<p>Use this service to send notifications about events in GitLab projects to your Microsoft Teams channels. <a href="https://docs.gitlab.com/ee/user/project/integrations/microsoft_teams.html">How do I configure this integration?</a></p>'
end
def webhook_placeholder
'https://outlook.office.com/webhook/…'
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'If selected, successful pipelines do not trigger a notification event.' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
::MicrosoftTeams::Notifier.new(webhook).ping(
title: message.project_name,
summary: message.summary,
activity: message.activity,
attachments: message.attachments
)
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
end
# frozen_string_literal: true
module Integrations
class Slack < BaseChatNotification
include SlackMattermost::Notifier
extend ::Gitlab::Utils::Override
SUPPORTED_EVENTS_FOR_USAGE_LOG = %w[
push issue confidential_issue merge_request note confidential_note
tag_push wiki_page deployment
].freeze
prop_accessor EVENT_CHANNEL['alert']
def title
'Slack notifications'
end
def description
'Send notifications about project events to Slack.'
end
def self.to_param
'slack'
end
def default_channel_placeholder
_('general, development')
end
def webhook_placeholder
'https://hooks.slack.com/services/…'
end
def supported_events
additional = []
additional << 'alert'
super + additional
end
def get_message(object_kind, data)
return Integrations::ChatMessage::AlertMessage.new(data) if object_kind == 'alert'
super
end
override :log_usage
def log_usage(event, user_id)
return unless user_id
return unless SUPPORTED_EVENTS_FOR_USAGE_LOG.include?(event)
key = "i_ecosystem_slack_service_#{event}_notification"
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(key, values: user_id)
end
end
end
# frozen_string_literal: true
module Integrations
class UnifyCircuit < BaseChatNotification
def title
'Unify Circuit'
end
def description
s_('Integrations|Send notifications about project events to Unify Circuit.')
end
def self.to_param
'unify_circuit'
end
def help
'This service sends notifications about projects events to a Unify Circuit conversation.<br />
To set up this service:
<ol>
<li><a href="https://www.circuit.com/unifyportalfaqdetail?articleId=164448">Set up an incoming webhook for your conversation</a>. All notifications will come to this conversation.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. https://circuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
response = Gitlab::HTTP.post(webhook, body: {
subject: message.project_name,
text: message.summary,
markdown: true
}.to_json)
response if response.success?
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
end
# frozen_string_literal: true
module Integrations
class WebexTeams < BaseChatNotification
include ActionView::Helpers::UrlHelper
def title
s_("WebexTeamsService|Webex Teams")
end
def description
s_("WebexTeamsService|Send notifications about project events to Webex Teams.")
end
def self.to_param
'webex_teams'
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/webex_teams'), target: '_blank', rel: 'noopener noreferrer'
s_("WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}") % { docs_link: docs_link.html_safe }
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "https://api.ciscospark.com/v1/webhooks/incoming/...", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: { markdown: message.summary }.to_json)
response if response.success?
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
end
...@@ -166,33 +166,33 @@ class Project < ApplicationRecord ...@@ -166,33 +166,33 @@ class Project < ApplicationRecord
has_one :confluence_service, class_name: 'Integrations::Confluence' has_one :confluence_service, class_name: 'Integrations::Confluence'
has_one :custom_issue_tracker_service, class_name: 'Integrations::CustomIssueTracker' has_one :custom_issue_tracker_service, class_name: 'Integrations::CustomIssueTracker'
has_one :datadog_service, class_name: 'Integrations::Datadog' has_one :datadog_service, class_name: 'Integrations::Datadog'
has_one :discord_service, class_name: 'Integrations::Discord'
has_one :drone_ci_service, class_name: 'Integrations::DroneCi' has_one :drone_ci_service, class_name: 'Integrations::DroneCi'
has_one :emails_on_push_service, class_name: 'Integrations::EmailsOnPush' has_one :emails_on_push_service, class_name: 'Integrations::EmailsOnPush'
has_one :ewm_service, class_name: 'Integrations::Ewm' has_one :ewm_service, class_name: 'Integrations::Ewm'
has_one :external_wiki_service, class_name: 'Integrations::ExternalWiki' has_one :external_wiki_service, class_name: 'Integrations::ExternalWiki'
has_one :flowdock_service, class_name: 'Integrations::Flowdock' has_one :flowdock_service, class_name: 'Integrations::Flowdock'
has_one :hangouts_chat_service, class_name: 'Integrations::HangoutsChat'
has_one :irker_service, class_name: 'Integrations::Irker' has_one :irker_service, class_name: 'Integrations::Irker'
has_one :jenkins_service, class_name: 'Integrations::Jenkins' has_one :jenkins_service, class_name: 'Integrations::Jenkins'
has_one :jira_service, class_name: 'Integrations::Jira' has_one :jira_service, class_name: 'Integrations::Jira'
has_one :mattermost_service, class_name: 'Integrations::Mattermost'
has_one :microsoft_teams_service, class_name: 'Integrations::MicrosoftTeams'
has_one :mock_ci_service, class_name: 'Integrations::MockCi' has_one :mock_ci_service, class_name: 'Integrations::MockCi'
has_one :packagist_service, class_name: 'Integrations::Packagist' has_one :packagist_service, class_name: 'Integrations::Packagist'
has_one :pipelines_email_service, class_name: 'Integrations::PipelinesEmail' has_one :pipelines_email_service, class_name: 'Integrations::PipelinesEmail'
has_one :pivotaltracker_service, class_name: 'Integrations::Pivotaltracker' has_one :pivotaltracker_service, class_name: 'Integrations::Pivotaltracker'
has_one :redmine_service, class_name: 'Integrations::Redmine' has_one :redmine_service, class_name: 'Integrations::Redmine'
has_one :slack_service, class_name: 'Integrations::Slack'
has_one :teamcity_service, class_name: 'Integrations::Teamcity' has_one :teamcity_service, class_name: 'Integrations::Teamcity'
has_one :unify_circuit_service, class_name: 'Integrations::UnifyCircuit'
has_one :webex_teams_service, class_name: 'Integrations::WebexTeams'
has_one :youtrack_service, class_name: 'Integrations::Youtrack' has_one :youtrack_service, class_name: 'Integrations::Youtrack'
has_one :discord_service
has_one :mattermost_slash_commands_service has_one :mattermost_slash_commands_service
has_one :mattermost_service
has_one :slack_slash_commands_service has_one :slack_slash_commands_service
has_one :slack_service
has_one :pushover_service has_one :pushover_service
has_one :prometheus_service, inverse_of: :project has_one :prometheus_service, inverse_of: :project
has_one :mock_monitoring_service has_one :mock_monitoring_service
has_one :microsoft_teams_service
has_one :hangouts_chat_service
has_one :unify_circuit_service
has_one :webex_teams_service
has_one :root_of_fork_network, has_one :root_of_fork_network,
foreign_key: 'root_project_id', foreign_key: 'root_project_id',
......
# frozen_string_literal: true
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Integration
include ChatMessage
include NotificationBranchSelection
SUPPORTED_EVENTS = %w[
push issue confidential_issue merge_request note confidential_note
tag_push pipeline wiki_page deployment
].freeze
SUPPORTED_EVENTS_FOR_LABEL_FILTER = %w[issue confidential_issue merge_request note confidential_note].freeze
EVENT_CHANNEL = proc { |event| "#{event}_channel" }
LABEL_NOTIFICATION_BEHAVIOURS = [
MATCH_ANY_LABEL = 'match_any',
MATCH_ALL_LABELS = 'match_all'
].freeze
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified, :labels_to_be_notified_behavior
# Custom serialized properties initialization
prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, public_url: true, if: :activated?
validates :labels_to_be_notified_behavior, inclusion: { in: LABEL_NOTIFICATION_BEHAVIOURS }, allow_blank: true
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
self.branches_to_be_notified = "default"
self.labels_to_be_notified_behavior = MATCH_ANY_LABEL
elsif !self.notify_only_default_branch.nil?
# In older versions, there was only a boolean property named
# `notify_only_default_branch`. Now we have a string property named
# `branches_to_be_notified`. Instead of doing a background migration, we
# opted to set a value for the new property based on the old one, if
# users hasn't specified one already. When users edit the service and
# selects a value for this new property, it will override everything.
self.branches_to_be_notified ||= notify_only_default_branch? ? "default" : "all"
end
end
def confidential_issue_channel
properties['confidential_issue_channel'].presence || properties['issue_channel']
end
def confidential_note_channel
properties['confidential_note_channel'].presence || properties['note_channel']
end
def self.supported_events
SUPPORTED_EVENTS
end
def fields
default_fields + build_event_channels
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}", required: true }.freeze,
{ type: 'text', name: 'username', placeholder: 'GitLab-integration' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'Do not send notifications for successful pipelines.' }.freeze,
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze,
{
type: 'text',
name: 'labels_to_be_notified',
placeholder: '~backend,~frontend',
help: 'Send notifications for issue, merge request, and comment events with the listed labels only. Leave blank to receive notifications for all events.'
}.freeze,
{
type: 'select',
name: 'labels_to_be_notified_behavior',
choices: [
['Match any of the labels', MATCH_ANY_LABEL],
['Match all of the labels', MATCH_ALL_LABELS]
]
}.freeze
].freeze
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
object_kind = data[:object_kind]
data = custom_data(data)
return unless notify_label?(data)
# 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
event_type = data[:event_type] || object_kind
channel_names = get_channel_field(event_type).presence || channel.presence
channels = channel_names&.split(',')&.map(&:strip)
opts = {}
opts[:channel] = channels if channels.present?
opts[:username] = username if username
if notify(message, opts)
log_usage(event_type, user_id_from_hook_data(data))
return true
end
false
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_placeholder
raise NotImplementedError
end
private
def log_usage(_, _)
# Implement in child class
end
def labels_to_be_notified_list
return [] if labels_to_be_notified.nil?
labels_to_be_notified.delete('~').split(',').map(&:strip)
end
def notify_label?(data)
return true unless SUPPORTED_EVENTS_FOR_LABEL_FILTER.include?(data[:object_kind]) && labels_to_be_notified.present?
labels = data[:labels] || data.dig(:issue, :labels) || data.dig(:merge_request, :labels) || data.dig(:object_attributes, :labels)
return false if labels.blank?
matching_labels = labels_to_be_notified_list & labels.pluck(:title)
if labels_to_be_notified_behavior == MATCH_ALL_LABELS
labels_to_be_notified_list.difference(matching_labels).empty?
else
matching_labels.any?
end
end
def user_id_from_hook_data(data)
data.dig(:user, :id) || data[:user_id]
end
# every notifier must implement this independently
def notify(message, opts)
raise NotImplementedError
end
def custom_data(data)
data.merge(project_url: project_url, project_name: project_name).with_indifferent_access
end
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
Integrations::ChatMessage::PushMessage.new(data) if notify_for_ref?(data)
when "issue"
Integrations::ChatMessage::IssueMessage.new(data) unless update?(data)
when "merge_request"
Integrations::ChatMessage::MergeMessage.new(data) unless update?(data)
when "note"
Integrations::ChatMessage::NoteMessage.new(data)
when "pipeline"
Integrations::ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
Integrations::ChatMessage::WikiPageMessage.new(data)
when "deployment"
Integrations::ChatMessage::DeploymentMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name) # rubocop:disable GitlabSecurity/PublicSend
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel_placeholder }
end
end
def event_channel_name(event)
EVENT_CHANNEL[event]
end
def project_name
project.full_name
end
def project_url
project.web_url
end
def update?(data)
data[:object_attributes][:action] == 'update'
end
def should_pipeline_be_notified?(data)
notify_for_ref?(data) && notify_for_pipeline?(data)
end
def notify_for_ref?(data)
return true if data[:object_kind] == 'tag_push'
return true if data.dig(:object_attributes, :tag)
notify_for_branch?(data)
end
def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end
end
# frozen_string_literal: true
require "discordrb/webhooks"
class DiscordService < ChatNotificationService
include ActionView::Helpers::UrlHelper
ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/.freeze
def title
s_("DiscordService|Discord Notifications")
end
def description
s_("DiscordService|Send notifications about project events to a Discord channel.")
end
def self.to_param
"discord"
end
def help
docs_link = link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/discord_notifications'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to a Discord channel. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def event_field(event)
# No-op.
end
def default_channel_placeholder
# No-op.
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page]
end
def default_fields
[
{ type: "text", name: "webhook", placeholder: "https://discordapp.com/api/webhooks/…", help: "URL to the webhook for the Discord channel." },
{ type: "checkbox", name: "notify_only_broken_pipelines" },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
client = Discordrb::Webhooks::Client.new(url: webhook)
client.execute do |builder|
builder.add_embed do |embed|
embed.author = Discordrb::Webhooks::EmbedAuthor.new(name: message.user_name, icon_url: message.user_avatar)
embed.description = (message.pretext + "\n" + Array.wrap(message.attachments).join("\n")).gsub(ATTACHMENT_REGEX, " \\k<entry> - \\k<name>\n")
end
end
rescue RestClient::Exception => error
log_error(error.message)
false
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
# frozen_string_literal: true
require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService
include ActionView::Helpers::UrlHelper
def title
'Google Chat'
end
def description
'Send notifications from GitLab to a room in Google Chat.'
end
def self.to_param
'hangouts_chat'
end
def help
docs_link = link_to _('How do I set up a Google Chat webhook?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/hangouts_chat'), target: '_blank', rel: 'noopener noreferrer'
s_('Before enabling this integration, create a webhook for the room in Google Chat where you want to receive notifications from this project. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def event_field(event)
end
def default_channel_placeholder
end
def webhook_placeholder
'https://chat.googleapis.com/v1/spaces…'
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
simple_text = parse_simple_text_message(message)
HangoutsChat::Sender.new(webhook).simple(simple_text)
end
def parse_simple_text_message(message)
header = message.pretext
return header if message.attachments.empty?
attachment = message.attachments.first
title = format_attachment_title(attachment)
body = attachment[:text]
[header, title, body].compact.join("\n")
end
def format_attachment_title(attachment)
return attachment[:title] unless attachment[:title_link]
"<#{attachment[:title_link]}|#{attachment[:title]}>"
end
end
# frozen_string_literal: true
class MattermostService < ChatNotificationService
include SlackMattermost::Notifier
include ActionView::Helpers::UrlHelper
def title
s_('Mattermost notifications')
end
def description
s_('Send notifications about project events to Mattermost channels.')
end
def self.to_param
'mattermost'
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/mattermost'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to Mattermost channels. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def default_channel_placeholder
'my-channel'
end
def webhook_placeholder
'http://mattermost.example.com/hooks/'
end
end
...@@ -22,17 +22,17 @@ class MattermostSlashCommandsService < SlashCommandsService ...@@ -22,17 +22,17 @@ class MattermostSlashCommandsService < SlashCommandsService
end end
def configure(user, params) def configure(user, params)
token = Mattermost::Command.new(user) token = ::Mattermost::Command.new(user)
.create(command(params)) .create(command(params))
update(active: true, token: token) if token update(active: true, token: token) if token
rescue Mattermost::Error => e rescue ::Mattermost::Error => e
[false, e.message] [false, e.message]
end end
def list_teams(current_user) def list_teams(current_user)
[Mattermost::Team.new(current_user).all, nil] [::Mattermost::Team.new(current_user).all, nil]
rescue Mattermost::Error => e rescue ::Mattermost::Error => e
[[], e.message] [[], e.message]
end end
......
# frozen_string_literal: true
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams notifications'
end
def description
'Send notifications about project events to Microsoft Teams.'
end
def self.to_param
'microsoft_teams'
end
def help
'<p>Use this service to send notifications about events in GitLab projects to your Microsoft Teams channels. <a href="https://docs.gitlab.com/ee/user/project/integrations/microsoft_teams.html">How do I configure this integration?</a></p>'
end
def webhook_placeholder
'https://outlook.office.com/webhook/…'
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'If selected, successful pipelines do not trigger a notification event.' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
MicrosoftTeams::Notifier.new(webhook).ping(
title: message.project_name,
summary: message.summary,
activity: message.activity,
attachments: message.attachments
)
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
...@@ -6,7 +6,7 @@ module SlackMattermost ...@@ -6,7 +6,7 @@ module SlackMattermost
def notify(message, opts) def notify(message, opts)
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client # See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
notifier = Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient)) notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
notifier.ping( notifier.ping(
message.pretext, message.pretext,
attachments: message.attachments, attachments: message.attachments,
......
# frozen_string_literal: true
class SlackService < ChatNotificationService
include SlackMattermost::Notifier
extend ::Gitlab::Utils::Override
SUPPORTED_EVENTS_FOR_USAGE_LOG = %w[
push issue confidential_issue merge_request note confidential_note
tag_push wiki_page deployment
].freeze
prop_accessor EVENT_CHANNEL['alert']
def title
'Slack notifications'
end
def description
'Send notifications about project events to Slack.'
end
def self.to_param
'slack'
end
def default_channel_placeholder
_('general, development')
end
def webhook_placeholder
'https://hooks.slack.com/services/…'
end
def supported_events
additional = []
additional << 'alert'
super + additional
end
def get_message(object_kind, data)
return Integrations::ChatMessage::AlertMessage.new(data) if object_kind == 'alert'
super
end
override :log_usage
def log_usage(event, user_id)
return unless user_id
return unless SUPPORTED_EVENTS_FOR_USAGE_LOG.include?(event)
key = "i_ecosystem_slack_service_#{event}_notification"
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(key, values: user_id)
end
end
...@@ -29,6 +29,6 @@ class SlackSlashCommandsService < SlashCommandsService ...@@ -29,6 +29,6 @@ class SlackSlashCommandsService < SlashCommandsService
private private
def format(text) def format(text)
Slack::Messenger::Util::LinkFormatter.format(text) if text ::Slack::Messenger::Util::LinkFormatter.format(text) if text
end end
end end
# frozen_string_literal: true
class UnifyCircuitService < ChatNotificationService
def title
'Unify Circuit'
end
def description
s_('Integrations|Send notifications about project events to Unify Circuit.')
end
def self.to_param
'unify_circuit'
end
def help
'This service sends notifications about projects events to a Unify Circuit conversation.<br />
To set up this service:
<ol>
<li><a href="https://www.circuit.com/unifyportalfaqdetail?articleId=164448">Set up an incoming webhook for your conversation</a>. All notifications will come to this conversation.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. https://circuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
response = Gitlab::HTTP.post(webhook, body: {
subject: message.project_name,
text: message.summary,
markdown: true
}.to_json)
response if response.success?
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
# frozen_string_literal: true
class WebexTeamsService < ChatNotificationService
include ActionView::Helpers::UrlHelper
def title
s_("WebexTeamsService|Webex Teams")
end
def description
s_("WebexTeamsService|Send notifications about project events to Webex Teams.")
end
def self.to_param
'webex_teams'
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/webex_teams'), target: '_blank', rel: 'noopener noreferrer'
s_("WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}") % { docs_link: docs_link.html_safe }
end
def event_field(event)
end
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "https://api.ciscospark.com/v1/webhooks/incoming/...", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
private
def notify(message, opts)
header = { 'Content-Type' => 'application/json' }
response = Gitlab::HTTP.post(webhook, headers: header, body: { markdown: message.summary }.to_json)
response if response.success?
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
...@@ -28,7 +28,7 @@ module Groups ...@@ -28,7 +28,7 @@ module Groups
@group.name ||= @group.path.dup @group.name ||= @group.path.dup
if create_chat_team? if create_chat_team?
response = Mattermost::CreateTeamService.new(@group, current_user).execute response = ::Mattermost::CreateTeamService.new(@group, current_user).execute
return @group if @group.errors.any? return @group if @group.errors.any?
@group.build_chat_team(name: response['name'], team_id: response['id']) @group.build_chat_team(name: response['name'], team_id: response['id'])
......
...@@ -9,8 +9,8 @@ module Mattermost ...@@ -9,8 +9,8 @@ module Mattermost
def execute def execute
# The user that creates the team will be Team Admin # The user that creates the team will be Team Admin
Mattermost::Team.new(current_user).create(@group.mattermost_team_params) ::Mattermost::Team.new(current_user).create(@group.mattermost_team_params)
rescue Mattermost::ClientError => e rescue ::Mattermost::ClientError => e
@group.errors.add(:mattermost_team, e.message) @group.errors.add(:mattermost_team, e.message)
end end
end end
......
...@@ -783,29 +783,29 @@ module API ...@@ -783,29 +783,29 @@ module API
::Integrations::Confluence, ::Integrations::Confluence,
::Integrations::CustomIssueTracker, ::Integrations::CustomIssueTracker,
::Integrations::Datadog, ::Integrations::Datadog,
::Integrations::Discord,
::Integrations::DroneCi, ::Integrations::DroneCi,
::Integrations::EmailsOnPush, ::Integrations::EmailsOnPush,
::Integrations::Ewm, ::Integrations::Ewm,
::Integrations::ExternalWiki, ::Integrations::ExternalWiki,
::Integrations::Flowdock, ::Integrations::Flowdock,
::Integrations::HangoutsChat,
::Integrations::Irker, ::Integrations::Irker,
::Integrations::Jenkins, ::Integrations::Jenkins,
::Integrations::Jira, ::Integrations::Jira,
::Integrations::Mattermost,
::Integrations::MicrosoftTeams,
::Integrations::Packagist, ::Integrations::Packagist,
::Integrations::PipelinesEmail, ::Integrations::PipelinesEmail,
::Integrations::Pivotaltracker, ::Integrations::Pivotaltracker,
::Integrations::Redmine, ::Integrations::Redmine,
::Integrations::Slack,
::Integrations::Teamcity, ::Integrations::Teamcity,
::Integrations::Youtrack, ::Integrations::Youtrack,
::DiscordService,
::HangoutsChatService,
::MattermostSlashCommandsService, ::MattermostSlashCommandsService,
::SlackSlashCommandsService, ::SlackSlashCommandsService,
::PrometheusService, ::PrometheusService,
::PushoverService, ::PushoverService
::SlackService,
::MattermostService,
::MicrosoftTeamsService
] ]
end end
......
...@@ -5,8 +5,9 @@ module Gitlab ...@@ -5,8 +5,9 @@ module Gitlab
class StiType < ActiveRecord::Type::String class StiType < ActiveRecord::Type::String
NAMESPACED_INTEGRATIONS = Set.new(%w( NAMESPACED_INTEGRATIONS = Set.new(%w(
Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog
DroneCi EmailsOnPush Ewm ExternalWiki Flowdock IssueTracker Irker Jenkins Jira MockCi Packagist Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker
PipelinesEmail Pivotaltracker Redmine Teamcity Youtrack Jenkins Jira Mattermost MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker
Redmine Slack Teamcity UnifyCircuit Youtrack WebexTeams
)).freeze )).freeze
def cast(value) def cast(value)
......
...@@ -63,7 +63,7 @@ module Gitlab ...@@ -63,7 +63,7 @@ module Gitlab
# Convert Markdown to slacks format # Convert Markdown to slacks format
def format(string) def format(string)
Slack::Messenger::Util::LinkFormatter.format(string) ::Slack::Messenger::Util::LinkFormatter.format(string)
end end
def resource_url def resource_url
......
# frozen_string_literal: true # frozen_string_literal: true
module Mattermost module Mattermost
ClientError = Class.new(Mattermost::Error) ClientError = Class.new(::Mattermost::Error)
class Client class Client
attr_reader :user attr_reader :user
...@@ -11,7 +11,7 @@ module Mattermost ...@@ -11,7 +11,7 @@ module Mattermost
end end
def with_session(&blk) def with_session(&blk)
Mattermost::Session.new(user).with_session(&blk) ::Mattermost::Session.new(user).with_session(&blk)
end end
private private
...@@ -52,12 +52,12 @@ module Mattermost ...@@ -52,12 +52,12 @@ module Mattermost
json_response = Gitlab::Json.parse(response.body, legacy_mode: true) json_response = Gitlab::Json.parse(response.body, legacy_mode: true)
unless response.success? unless response.success?
raise Mattermost::ClientError, json_response['message'] || 'Undefined error' raise ::Mattermost::ClientError, json_response['message'] || 'Undefined error'
end end
json_response json_response
rescue JSON::JSONError rescue JSON::JSONError
raise Mattermost::ClientError, 'Cannot parse response' raise ::Mattermost::ClientError, 'Cannot parse response'
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
module Mattermost module Mattermost
class NoSessionError < Mattermost::Error class NoSessionError < ::Mattermost::Error
def message def message
'No session could be set up, is Mattermost configured with Single Sign On?' 'No session could be set up, is Mattermost configured with Single Sign On?'
end end
end end
ConnectionError = Class.new(Mattermost::Error) ConnectionError = Class.new(::Mattermost::Error)
# This class' prime objective is to obtain a session token on a Mattermost # This class' prime objective is to obtain a session token on a Mattermost
# instance with SSO configured where this GitLab instance is the provider. # instance with SSO configured where this GitLab instance is the provider.
...@@ -42,7 +42,7 @@ module Mattermost ...@@ -42,7 +42,7 @@ module Mattermost
yield self yield self
rescue Errno::ECONNREFUSED => e rescue Errno::ECONNREFUSED => e
Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n")) Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n"))
raise Mattermost::NoSessionError raise ::Mattermost::NoSessionError
ensure ensure
destroy destroy
end end
...@@ -100,11 +100,11 @@ module Mattermost ...@@ -100,11 +100,11 @@ module Mattermost
end end
def create def create
raise Mattermost::NoSessionError unless oauth_uri raise ::Mattermost::NoSessionError unless oauth_uri
raise Mattermost::NoSessionError unless token_uri raise ::Mattermost::NoSessionError unless token_uri
@token = request_token @token = request_token
raise Mattermost::NoSessionError unless @token raise ::Mattermost::NoSessionError unless @token
@headers = { @headers = {
Authorization: "Bearer #{@token}" Authorization: "Bearer #{@token}"
...@@ -174,9 +174,9 @@ module Mattermost ...@@ -174,9 +174,9 @@ module Mattermost
def handle_exceptions def handle_exceptions
yield yield
rescue Gitlab::HTTP::Error => e rescue Gitlab::HTTP::Error => e
raise Mattermost::ConnectionError, e.message raise ::Mattermost::ConnectionError, e.message
rescue Errno::ECONNREFUSED => e rescue Errno::ECONNREFUSED => e
raise Mattermost::ConnectionError, e.message raise ::Mattermost::ConnectionError, e.message
end end
def parse_cookie(response) def parse_cookie(response)
......
...@@ -32,7 +32,7 @@ module MicrosoftTeams ...@@ -32,7 +32,7 @@ module MicrosoftTeams
result['title'] = title result['title'] = title
result['summary'] = summary result['summary'] = summary
result['sections'] << MicrosoftTeams::Activity.new(**activity).prepare result['sections'] << ::MicrosoftTeams::Activity.new(**activity).prepare
unless attachments.blank? unless attachments.blank?
result['sections'] << { text: attachments } result['sections'] << { text: attachments }
......
...@@ -53,7 +53,7 @@ RSpec.describe Projects::MattermostsController do ...@@ -53,7 +53,7 @@ RSpec.describe Projects::MattermostsController do
context 'the request is succesull' do context 'the request is succesull' do
before do before do
allow_next_instance_of(Mattermost::Command) do |instance| allow_next_instance_of(::Mattermost::Command) do |instance|
allow(instance).to receive(:create).and_return('token') allow(instance).to receive(:create).and_return('token')
end end
end end
......
...@@ -47,7 +47,7 @@ RSpec.describe Projects::ServicesController do ...@@ -47,7 +47,7 @@ RSpec.describe Projects::ServicesController do
let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') } let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
it 'returns success' do it 'returns success' do
allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true) allow_any_instance_of(::MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
put :test, params: project_params put :test, params: project_params
...@@ -145,7 +145,7 @@ RSpec.describe Projects::ServicesController do ...@@ -145,7 +145,7 @@ RSpec.describe Projects::ServicesController do
end end
it 'returns an error response when a network exception is raised' do it 'returns an error response when a network exception is raised' do
expect_next(SlackService).to receive(:test).and_raise(Errno::ECONNREFUSED) expect_next(Integrations::Slack).to receive(:test).and_raise(Errno::ECONNREFUSED)
put :test, params: project_params put :test, params: project_params
......
...@@ -160,7 +160,7 @@ FactoryBot.define do ...@@ -160,7 +160,7 @@ FactoryBot.define do
password { 'my-secret-password' } password { 'my-secret-password' }
end end
factory :slack_service do factory :slack_service, class: 'Integrations::Slack' do
project project
active { true } active { true }
webhook { 'https://slack.service.url' } webhook { 'https://slack.service.url' }
......
...@@ -20,7 +20,7 @@ RSpec.describe 'User activates Slack notifications', :js do ...@@ -20,7 +20,7 @@ RSpec.describe 'User activates Slack notifications', :js do
end end
context 'when service is already configured' do context 'when service is already configured' do
let(:service) { SlackService.new } let(:service) { Integrations::Slack.new }
let(:project) { create(:project, slack_service: service) } let(:project) { create(:project, slack_service: service) }
before do before do
......
...@@ -14,13 +14,13 @@ RSpec.describe Mattermost::Client do ...@@ -14,13 +14,13 @@ RSpec.describe Mattermost::Client do
it 'yields an error on malformed JSON' do it 'yields an error on malformed JSON' do
bad_json = Struct::Request.new("I'm not json", true) bad_json = Struct::Request.new("I'm not json", true)
expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError) expect { subject.send(:json_response, bad_json) }.to raise_error(::Mattermost::ClientError)
end end
it 'shows a client error if the request was unsuccessful' do it 'shows a client error if the request was unsuccessful' do
bad_request = Struct::Request.new("true", false) bad_request = Struct::Request.new("true", false)
expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError) expect { subject.send(:json_response, bad_request) }.to raise_error(::Mattermost::ClientError)
end end
end end
end end
...@@ -6,10 +6,10 @@ RSpec.describe Mattermost::Command do ...@@ -6,10 +6,10 @@ RSpec.describe Mattermost::Command do
let(:params) { { 'token' => 'token', team_id: 'abc' } } let(:params) { { 'token' => 'token', team_id: 'abc' } }
before do before do
session = Mattermost::Session.new(nil) session = ::Mattermost::Session.new(nil)
session.base_uri = 'http://mattermost.example.com' session.base_uri = 'http://mattermost.example.com'
allow_any_instance_of(Mattermost::Client).to receive(:with_session) allow_any_instance_of(::Mattermost::Client).to receive(:with_session)
.and_yield(session) .and_yield(session)
end end
...@@ -57,7 +57,7 @@ RSpec.describe Mattermost::Command do ...@@ -57,7 +57,7 @@ RSpec.describe Mattermost::Command do
end end
it 'raises an error with message' do it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.') expect { subject }.to raise_error(::Mattermost::Error, 'This trigger word is already in use. Please choose another word.')
end end
end end
end end
......
...@@ -33,7 +33,7 @@ RSpec.describe Mattermost::Session, type: :request do ...@@ -33,7 +33,7 @@ RSpec.describe Mattermost::Session, type: :request do
context 'without oauth uri' do context 'without oauth uri' do
it 'makes a request to the oauth uri' do it 'makes a request to the oauth uri' do
expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) expect { subject.with_session }.to raise_error(::Mattermost::NoSessionError)
end end
end end
...@@ -49,7 +49,7 @@ RSpec.describe Mattermost::Session, type: :request do ...@@ -49,7 +49,7 @@ RSpec.describe Mattermost::Session, type: :request do
it 'can not create a session' do it 'can not create a session' do
expect do expect do
subject.with_session subject.with_session
end.to raise_error(Mattermost::NoSessionError) end.to raise_error(::Mattermost::NoSessionError)
end end
end end
...@@ -113,13 +113,13 @@ RSpec.describe Mattermost::Session, type: :request do ...@@ -113,13 +113,13 @@ RSpec.describe Mattermost::Session, type: :request do
expect_to_cancel_exclusive_lease(lease_key, 'uuid') expect_to_cancel_exclusive_lease(lease_key, 'uuid')
# Cannot set up a session, but we should still cancel the lease # Cannot set up a session, but we should still cancel the lease
expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) expect { subject.with_session }.to raise_error(::Mattermost::NoSessionError)
end end
it 'returns a NoSessionError error without lease' do it 'returns a NoSessionError error without lease' do
stub_exclusive_lease_taken(lease_key) stub_exclusive_lease_taken(lease_key)
expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) expect { subject.with_session }.to raise_error(::Mattermost::NoSessionError)
end end
end end
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe Mattermost::Team do ...@@ -7,7 +7,7 @@ RSpec.describe Mattermost::Team do
session = Mattermost::Session.new(nil) session = Mattermost::Session.new(nil)
session.base_uri = 'http://mattermost.example.com' session.base_uri = 'http://mattermost.example.com'
allow_any_instance_of(Mattermost::Client).to receive(:with_session) allow_any_instance_of(::Mattermost::Client).to receive(:with_session)
.and_yield(session) .and_yield(session)
end end
...@@ -65,7 +65,7 @@ RSpec.describe Mattermost::Team do ...@@ -65,7 +65,7 @@ RSpec.describe Mattermost::Team do
end end
it 'raises an error with message' do it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.') expect { subject }.to raise_error(::Mattermost::Error, 'Cannot list teams.')
end end
end end
end end
...@@ -123,7 +123,7 @@ RSpec.describe Mattermost::Team do ...@@ -123,7 +123,7 @@ RSpec.describe Mattermost::Team do
end end
it 'raises an error with message' do it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, 'A team with that name already exists') expect { subject }.to raise_error(::Mattermost::Error, 'A team with that name already exists')
end end
end end
end end
...@@ -169,7 +169,7 @@ RSpec.describe Mattermost::Team do ...@@ -169,7 +169,7 @@ RSpec.describe Mattermost::Team do
end end
it 'raises an error with message' do it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, "We couldn't find the existing team") expect { subject }.to raise_error(::Mattermost::Error, "We couldn't find the existing team")
end end
end end
end end
......
...@@ -672,7 +672,7 @@ RSpec.describe Integration do ...@@ -672,7 +672,7 @@ RSpec.describe Integration do
expect(described_class.service_name_to_model('asana')).to eq(Integrations::Asana) expect(described_class.service_name_to_model('asana')).to eq(Integrations::Asana)
# TODO We can remove this test when all models have been namespaced: # TODO We can remove this test when all models have been namespaced:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955 # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955
expect(described_class.service_name_to_model('webex_teams')).to eq(WebexTeamsService) expect(described_class.service_name_to_model('pushover')).to eq(PushoverService)
end end
it 'raises an error if service name is invalid' do it 'raises an error if service name is invalid' do
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ChatNotificationService do RSpec.describe Integrations::BaseChatNotification do
describe 'Associations' do describe 'Associations' do
before do before do
allow(subject).to receive(:activated?).and_return(true) allow(subject).to receive(:activated?).and_return(true)
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require "spec_helper" require "spec_helper"
RSpec.describe DiscordService do RSpec.describe Integrations::Discord do
it_behaves_like "chat service", "Discord notifications" do it_behaves_like "chat integration", "Discord notifications" do
let(:client) { Discordrb::Webhooks::Client } let(:client) { Discordrb::Webhooks::Client }
let(:client_arguments) { { url: webhook_url } } let(:client_arguments) { { url: webhook_url } }
let(:payload) do let(:payload) do
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require "spec_helper" require "spec_helper"
RSpec.describe HangoutsChatService do RSpec.describe Integrations::HangoutsChat do
it_behaves_like "chat service", "Hangouts Chat" do it_behaves_like "chat integration", "Hangouts Chat" do
let(:client) { HangoutsChat::Sender } let(:client) { HangoutsChat::Sender }
let(:client_arguments) { webhook_url } let(:client_arguments) { webhook_url }
let(:payload) do let(:payload) do
......
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe MattermostService do RSpec.describe Integrations::Mattermost do
it_behaves_like "slack or mattermost notifications", "Mattermost" it_behaves_like "slack or mattermost notifications", "Mattermost"
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe MicrosoftTeamsService do RSpec.describe Integrations::MicrosoftTeams do
let(:chat_service) { described_class.new } let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' } let(:webhook_url) { 'https://example.gitlab.com/' }
...@@ -64,7 +64,7 @@ RSpec.describe MicrosoftTeamsService do ...@@ -64,7 +64,7 @@ RSpec.describe MicrosoftTeamsService do
end end
it 'specifies the webhook when it is configured' do it 'specifies the webhook when it is configured' do
expect(MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(double(:microsoft_teams_service).as_null_object) expect(::MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(double(:microsoft_teams_service).as_null_object)
chat_service.execute(push_sample_data) chat_service.execute(push_sample_data)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe SlackService do RSpec.describe Integrations::Slack do
it_behaves_like "slack or mattermost notifications", 'Slack' it_behaves_like "slack or mattermost notifications", 'Slack'
describe '#execute' do describe '#execute' do
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require "spec_helper" require "spec_helper"
RSpec.describe UnifyCircuitService do RSpec.describe Integrations::UnifyCircuit do
it_behaves_like "chat service", "Unify Circuit" do it_behaves_like "chat integration", "Unify Circuit" do
let(:client_arguments) { webhook_url } let(:client_arguments) { webhook_url }
let(:payload) do let(:payload) do
{ {
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require "spec_helper" require "spec_helper"
RSpec.describe WebexTeamsService do RSpec.describe Integrations::WebexTeams do
it_behaves_like "chat service", "Webex Teams" do it_behaves_like "chat integration", "Webex Teams" do
let(:client_arguments) { webhook_url } let(:client_arguments) { webhook_url }
let(:payload) do let(:payload) do
{ {
......
...@@ -11,10 +11,10 @@ RSpec.describe MattermostSlashCommandsService do ...@@ -11,10 +11,10 @@ RSpec.describe MattermostSlashCommandsService do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
session = Mattermost::Session.new(nil) session = ::Mattermost::Session.new(nil)
session.base_uri = 'http://mattermost.example.com' session.base_uri = 'http://mattermost.example.com'
allow_any_instance_of(Mattermost::Client).to receive(:with_session) allow_any_instance_of(::Mattermost::Client).to receive(:with_session)
.and_yield(session) .and_yield(session)
end end
......
...@@ -5250,7 +5250,7 @@ RSpec.describe Project, factory_default: :keep do ...@@ -5250,7 +5250,7 @@ RSpec.describe Project, factory_default: :keep do
it 'executes services with the specified scope' do it 'executes services with the specified scope' do
data = 'any data' data = 'any data'
expect_next_found_instance_of(SlackService) do |instance| expect_next_found_instance_of(Integrations::Slack) do |instance|
expect(instance).to receive(:async_execute).with(data).once expect(instance).to receive(:async_execute).with(data).once
end end
...@@ -5258,7 +5258,7 @@ RSpec.describe Project, factory_default: :keep do ...@@ -5258,7 +5258,7 @@ RSpec.describe Project, factory_default: :keep do
end end
it 'does not execute services that don\'t match the specified scope' do it 'does not execute services that don\'t match the specified scope' do
expect(SlackService).not_to receive(:allocate).and_wrap_original do |method| expect(Integrations::Slack).not_to receive(:allocate).and_wrap_original do |method|
method.call.tap do |instance| method.call.tap do |instance|
expect(instance).not_to receive(:async_execute) expect(instance).not_to receive(:async_execute)
end end
......
...@@ -140,7 +140,7 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -140,7 +140,7 @@ RSpec.describe Groups::CreateService, '#execute' do
end end
it 'create the chat team with the group' do it 'create the chat team with the group' do
allow_any_instance_of(Mattermost::Team).to receive(:create) allow_any_instance_of(::Mattermost::Team).to receive(:create)
.and_return({ 'name' => 'tanuki', 'id' => 'lskdjfwlekfjsdifjj' }) .and_return({ 'name' => 'tanuki', 'id' => 'lskdjfwlekfjsdifjj' })
expect { subject }.to change { ChatTeam.count }.from(0).to(1) expect { subject }.to change { ChatTeam.count }.from(0).to(1)
......
...@@ -41,7 +41,7 @@ RSpec.describe Groups::DestroyService do ...@@ -41,7 +41,7 @@ RSpec.describe Groups::DestroyService do
let!(:chat_team) { create(:chat_team, namespace: group) } let!(:chat_team) { create(:chat_team, namespace: group) }
it 'destroys the team too' do it 'destroys the team too' do
expect_next_instance_of(Mattermost::Team) do |instance| expect_next_instance_of(::Mattermost::Team) do |instance|
expect(instance).to receive(:destroy) expect(instance).to receive(:destroy)
end end
......
...@@ -81,7 +81,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -81,7 +81,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
shared_examples 'calls the service API with the event message' do |event_message| shared_examples 'calls the service API with the event message' do |event_message|
specify do specify do
expect_next_instance_of(Slack::Messenger) do |messenger| expect_next_instance_of(::Slack::Messenger) do |messenger|
expect(messenger).to receive(:ping).with(event_message, anything).and_call_original expect(messenger).to receive(:ping).with(event_message, anything).and_call_original
end end
...@@ -95,7 +95,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -95,7 +95,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { username: 'slack_username' } } let(:chat_service_params) { { username: 'slack_username' } }
it 'uses the username as an option' do it 'uses the username as an option' do
expect(Slack::Messenger).to execute_with_options(username: 'slack_username') expect(::Slack::Messenger).to execute_with_options(username: 'slack_username')
execute_service execute_service
end end
...@@ -110,7 +110,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -110,7 +110,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { push_channel: 'random' } } let(:chat_service_params) { { push_channel: 'random' } }
it 'uses the right channel for push event' do it 'uses the right channel for push event' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -136,7 +136,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -136,7 +136,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { issue_channel: 'random' } } let(:chat_service_params) { { issue_channel: 'random' } }
it 'uses the right channel for issue event' do it 'uses the right channel for issue event' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -147,7 +147,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -147,7 +147,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
end end
it 'falls back to issue channel' do it 'falls back to issue channel' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -156,7 +156,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -156,7 +156,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } } let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } }
it 'uses the confidential issue channel when it is defined' do it 'uses the confidential issue channel when it is defined' do
expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) expect(::Slack::Messenger).to execute_with_options(channel: ['confidential'])
execute_service execute_service
end end
...@@ -175,7 +175,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -175,7 +175,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { merge_request_channel: 'random' } } let(:chat_service_params) { { merge_request_channel: 'random' } }
it 'uses the right channel for merge request event' do it 'uses the right channel for merge request event' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -192,7 +192,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -192,7 +192,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { wiki_page_channel: 'random' } } let(:chat_service_params) { { wiki_page_channel: 'random' } }
it 'uses the right channel for wiki event' do it 'uses the right channel for wiki event' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -216,7 +216,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -216,7 +216,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { note_channel: 'random' } } let(:chat_service_params) { { note_channel: 'random' } }
it 'uses the right channel' do it 'uses the right channel' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -227,7 +227,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -227,7 +227,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
end end
it 'falls back to note channel' do it 'falls back to note channel' do
expect(Slack::Messenger).to execute_with_options(channel: ['random']) expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
execute_service execute_service
end end
...@@ -236,7 +236,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| ...@@ -236,7 +236,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } } let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } }
it 'uses confidential channel' do it 'uses confidential channel' do
expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) expect(::Slack::Messenger).to execute_with_options(channel: ['confidential'])
execute_service execute_service
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