diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 9d3fc7f956bbcadb1b19d11cbddfe7b9ad963392..80fc20610e786a5d68749859c134ed928afe4bb9 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1648,20 +1648,13 @@ Gitlab/NamespacedClass: - 'app/models/project_services/alerts_service.rb' - 'app/models/project_services/alerts_service_data.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/microsoft_teams_service.rb' - 'app/models/project_services/mock_monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/prometheus_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/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_snippet.rb' - 'app/models/project_statistics.rb' diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb index 6e39d7e2204b3f2736aad53e01c90f84ff35ebed..ee786ae6cb7426f23c68a1a1bda9524b728197f5 100644 --- a/app/models/chat_team.rb +++ b/app/models/chat_team.rb @@ -7,8 +7,8 @@ class ChatTeam < ApplicationRecord belongs_to :namespace def remove_mattermost_team(current_user) - Mattermost::Team.new(current_user).destroy(team_id: team_id) - rescue Mattermost::ClientError => e + ::Mattermost::Team.new(current_user).destroy(team_id: team_id) + rescue ::Mattermost::ClientError => e # 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 # in the latter case, we can't recover by retrying, so we just log what happened diff --git a/app/models/concerns/notification_branch_selection.rb b/app/models/concerns/notification_branch_selection.rb index 2354335469a2a4114f15087b2fcaef007ca53db9..18ec996c3df75c65cc3f2dace9049281025885e0 100644 --- a/app/models/concerns/notification_branch_selection.rb +++ b/app/models/concerns/notification_branch_selection.rb @@ -2,7 +2,7 @@ # Concern handling functionality around deciding whether to send notification # for activities on a specified branch or not. Will be included in -# ChatNotificationService and PipelinesEmailService classes. +# Integrations::BaseChatNotification and PipelinesEmailService classes. module NotificationBranchSelection extend ActiveSupport::Concern diff --git a/app/models/integrations/base_chat_notification.rb b/app/models/integrations/base_chat_notification.rb new file mode 100644 index 0000000000000000000000000000000000000000..5eae8bce92a8f5be5ebd305967d0a6bed5d92ec6 --- /dev/null +++ b/app/models/integrations/base_chat_notification.rb @@ -0,0 +1,255 @@ +# 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 diff --git a/app/models/integrations/chat_message/base_message.rb b/app/models/integrations/chat_message/base_message.rb index 2f70384d3b9dbd23e6342d5e8c55d8495d33da2d..afe3ffc45a0e3e4de7e73ff1fa40fc54c44cd6df 100644 --- a/app/models/integrations/chat_message/base_message.rb +++ b/app/models/integrations/chat_message/base_message.rb @@ -58,7 +58,7 @@ def message end def format(string) - Slack::Messenger::Util::LinkFormatter.format(format_relative_links(string)) + ::Slack::Messenger::Util::LinkFormatter.format(format_relative_links(string)) end def format_relative_links(string) diff --git a/app/models/integrations/chat_message/pipeline_message.rb b/app/models/integrations/chat_message/pipeline_message.rb index a0f6f582e4c85105831079d7abfbc5ee6a0f1f6a..a3f68d34035a59898bfd965d187f0f043f8e68ab 100644 --- a/app/models/integrations/chat_message/pipeline_message.rb +++ b/app/models/integrations/chat_message/pipeline_message.rb @@ -105,7 +105,7 @@ def actually_failed_jobs(builds) def failed_stages_field { 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 } end @@ -113,7 +113,7 @@ def failed_stages_field def failed_jobs_field { 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 } end @@ -130,12 +130,12 @@ def attachments_fields fields = [ { 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 }, { title: s_("ChatMessage|Commit"), - value: Slack::Messenger::Util::LinkFormatter.format(commit_link), + value: ::Slack::Messenger::Util::LinkFormatter.format(commit_link), short: true } ] diff --git a/app/models/integrations/chat_message/push_message.rb b/app/models/integrations/chat_message/push_message.rb index 0952986e9239029ebb8f86d45f0d6b353055c822..fabd214633b81e7352a25e87db5b912345a23385 100644 --- a/app/models/integrations/chat_message/push_message.rb +++ b/app/models/integrations/chat_message/push_message.rb @@ -49,7 +49,7 @@ def message end def format(string) - Slack::Messenger::Util::LinkFormatter.format(string) + ::Slack::Messenger::Util::LinkFormatter.format(string) end def commit_messages diff --git a/app/models/integrations/discord.rb b/app/models/integrations/discord.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef6d46fd3d3e9ac4d1464bbb24e3144319cf6a1b --- /dev/null +++ b/app/models/integrations/discord.rb @@ -0,0 +1,68 @@ +# 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 diff --git a/app/models/integrations/hangouts_chat.rb b/app/models/integrations/hangouts_chat.rb new file mode 100644 index 0000000000000000000000000000000000000000..5ca00db7946a90886517a6f19cb2658fe7b7d72f --- /dev/null +++ b/app/models/integrations/hangouts_chat.rb @@ -0,0 +1,73 @@ +# 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 diff --git a/app/models/integrations/mattermost.rb b/app/models/integrations/mattermost.rb new file mode 100644 index 0000000000000000000000000000000000000000..97bb4342105edd9880a2cf9c4925427995eb0216 --- /dev/null +++ b/app/models/integrations/mattermost.rb @@ -0,0 +1,33 @@ +# 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 diff --git a/app/models/integrations/microsoft_teams.rb b/app/models/integrations/microsoft_teams.rb new file mode 100644 index 0000000000000000000000000000000000000000..91e6800f03cf42b7f8b58c4448f39ebfdad07724 --- /dev/null +++ b/app/models/integrations/microsoft_teams.rb @@ -0,0 +1,59 @@ +# 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 diff --git a/app/models/integrations/slack.rb b/app/models/integrations/slack.rb new file mode 100644 index 0000000000000000000000000000000000000000..35b376ce5f28963de7c8f8eb8fc622273ea1560b --- /dev/null +++ b/app/models/integrations/slack.rb @@ -0,0 +1,59 @@ +# 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 diff --git a/app/models/integrations/unify_circuit.rb b/app/models/integrations/unify_circuit.rb new file mode 100644 index 0000000000000000000000000000000000000000..03363c7c8b061170d081570b93da5b3543149ed6 --- /dev/null +++ b/app/models/integrations/unify_circuit.rb @@ -0,0 +1,62 @@ +# 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 diff --git a/app/models/integrations/webex_teams.rb b/app/models/integrations/webex_teams.rb new file mode 100644 index 0000000000000000000000000000000000000000..3f42033103539f292169deceff477d458deb5d4a --- /dev/null +++ b/app/models/integrations/webex_teams.rb @@ -0,0 +1,56 @@ +# 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 diff --git a/app/models/project.rb b/app/models/project.rb index efdcbe9e50b5538971a44201ad6b1020f7183dfe..45f9b8ac3442bebc05e0918ec6e3276dd4f4c9dd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -166,33 +166,33 @@ class Project < ApplicationRecord has_one :confluence_service, class_name: 'Integrations::Confluence' has_one :custom_issue_tracker_service, class_name: 'Integrations::CustomIssueTracker' 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 :emails_on_push_service, class_name: 'Integrations::EmailsOnPush' has_one :ewm_service, class_name: 'Integrations::Ewm' has_one :external_wiki_service, class_name: 'Integrations::ExternalWiki' 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 :jenkins_service, class_name: 'Integrations::Jenkins' 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 :packagist_service, class_name: 'Integrations::Packagist' has_one :pipelines_email_service, class_name: 'Integrations::PipelinesEmail' has_one :pivotaltracker_service, class_name: 'Integrations::Pivotaltracker' 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 :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 :discord_service has_one :mattermost_slash_commands_service - has_one :mattermost_service has_one :slack_slash_commands_service - has_one :slack_service has_one :pushover_service has_one :prometheus_service, inverse_of: :project 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, foreign_key: 'root_project_id', diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb deleted file mode 100644 index 7624dd0d62c58a51ece7cbc4e4cbd9101c193f51..0000000000000000000000000000000000000000 --- a/app/models/project_services/chat_notification_service.rb +++ /dev/null @@ -1,252 +0,0 @@ -# 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 diff --git a/app/models/project_services/discord_service.rb b/app/models/project_services/discord_service.rb deleted file mode 100644 index d7adf63fde4cc6dcfa9ef6590e8f7c5457509d60..0000000000000000000000000000000000000000 --- a/app/models/project_services/discord_service.rb +++ /dev/null @@ -1,66 +0,0 @@ -# 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 diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb deleted file mode 100644 index 6e7708a169fb34d292e353cbfd9a3536a5e0723c..0000000000000000000000000000000000000000 --- a/app/models/project_services/hangouts_chat_service.rb +++ /dev/null @@ -1,71 +0,0 @@ -# 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 diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb deleted file mode 100644 index 732a7c32a03d8eb8fbefb13d498e9172b6cefa55..0000000000000000000000000000000000000000 --- a/app/models/project_services/mattermost_service.rb +++ /dev/null @@ -1,31 +0,0 @@ -# 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 diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 60235a09dcde0ae510e9b9de66a44171c07e1712..5ee57aa3737613f4fa7073090a8e401ad8f0cadc 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -22,17 +22,17 @@ def self.to_param end def configure(user, params) - token = Mattermost::Command.new(user) + token = ::Mattermost::Command.new(user) .create(command(params)) update(active: true, token: token) if token - rescue Mattermost::Error => e + rescue ::Mattermost::Error => e [false, e.message] end def list_teams(current_user) - [Mattermost::Team.new(current_user).all, nil] - rescue Mattermost::Error => e + [::Mattermost::Team.new(current_user).all, nil] + rescue ::Mattermost::Error => e [[], e.message] end diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb deleted file mode 100644 index 1d2067067da76730d140ca74c1b6a84f73952785..0000000000000000000000000000000000000000 --- a/app/models/project_services/microsoft_teams_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# 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 diff --git a/app/models/project_services/slack_mattermost/notifier.rb b/app/models/project_services/slack_mattermost/notifier.rb index 1a78cea5933c0a354dd7fc864fd82b388f99112c..ae7ae02b8f0651e5664d912164aa42f7ba80b9dd 100644 --- a/app/models/project_services/slack_mattermost/notifier.rb +++ b/app/models/project_services/slack_mattermost/notifier.rb @@ -6,7 +6,7 @@ module Notifier def notify(message, opts) # 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( message.pretext, attachments: message.attachments, diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb deleted file mode 100644 index 92a46f8d01f5c2cd52cf6588f2094bdbdb646bf1..0000000000000000000000000000000000000000 --- a/app/models/project_services/slack_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# 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 diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 548f3623504dffedd0d2bfc14ed72fa36dc9b37a..652742c171037f01a75057e26b9d4f92bec48b49 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -29,6 +29,6 @@ def chat_responder private def format(text) - Slack::Messenger::Util::LinkFormatter.format(text) if text + ::Slack::Messenger::Util::LinkFormatter.format(text) if text end end diff --git a/app/models/project_services/unify_circuit_service.rb b/app/models/project_services/unify_circuit_service.rb deleted file mode 100644 index 5f43388e1c98d0061cafc3897409d51850344039..0000000000000000000000000000000000000000 --- a/app/models/project_services/unify_circuit_service.rb +++ /dev/null @@ -1,60 +0,0 @@ -# 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 diff --git a/app/models/project_services/webex_teams_service.rb b/app/models/project_services/webex_teams_service.rb deleted file mode 100644 index 3d92d3bb85e2896059b8caf23e755e18f06c0827..0000000000000000000000000000000000000000 --- a/app/models/project_services/webex_teams_service.rb +++ /dev/null @@ -1,54 +0,0 @@ -# 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 diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 8e8efe7d555df8f93c4c493ed7810a2cd3005c29..f900927793addd71ca4cb698517a12104b0b26aa 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -28,7 +28,7 @@ def execute @group.name ||= @group.path.dup 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? @group.build_chat_team(name: response['name'], team_id: response['id']) diff --git a/app/services/mattermost/create_team_service.rb b/app/services/mattermost/create_team_service.rb index 2cbcaaad5e1b757d32703f58158913fca5e6ce77..9f6efab1e43686312fb509a182d574eb180de5b0 100644 --- a/app/services/mattermost/create_team_service.rb +++ b/app/services/mattermost/create_team_service.rb @@ -9,8 +9,8 @@ def initialize(group, current_user) def execute # The user that creates the team will be Team Admin - Mattermost::Team.new(current_user).create(@group.mattermost_team_params) - rescue Mattermost::ClientError => e + ::Mattermost::Team.new(current_user).create(@group.mattermost_team_params) + rescue ::Mattermost::ClientError => e @group.errors.add(:mattermost_team, e.message) end end diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 0c139f30a0bf0c4bfcee4f1a8bae00371b6ecb2f..784c12a89fcfa05e7c5dbfe468adcd50ef6c71de 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -783,29 +783,29 @@ def self.service_classes ::Integrations::Confluence, ::Integrations::CustomIssueTracker, ::Integrations::Datadog, + ::Integrations::Discord, ::Integrations::DroneCi, ::Integrations::EmailsOnPush, ::Integrations::Ewm, ::Integrations::ExternalWiki, ::Integrations::Flowdock, + ::Integrations::HangoutsChat, ::Integrations::Irker, ::Integrations::Jenkins, ::Integrations::Jira, + ::Integrations::Mattermost, + ::Integrations::MicrosoftTeams, ::Integrations::Packagist, ::Integrations::PipelinesEmail, ::Integrations::Pivotaltracker, ::Integrations::Redmine, + ::Integrations::Slack, ::Integrations::Teamcity, ::Integrations::Youtrack, - ::DiscordService, - ::HangoutsChatService, ::MattermostSlashCommandsService, ::SlackSlashCommandsService, ::PrometheusService, - ::PushoverService, - ::SlackService, - ::MattermostService, - ::MicrosoftTeamsService + ::PushoverService ] end diff --git a/lib/gitlab/integrations/sti_type.rb b/lib/gitlab/integrations/sti_type.rb index f043a8fa0608a0b779cb2c8d498ce57c5d230131..c5f3837277761084bf80f5ea32b423d545c43340 100644 --- a/lib/gitlab/integrations/sti_type.rb +++ b/lib/gitlab/integrations/sti_type.rb @@ -5,8 +5,9 @@ module Integrations class StiType < ActiveRecord::Type::String NAMESPACED_INTEGRATIONS = Set.new(%w( Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog - DroneCi EmailsOnPush Ewm ExternalWiki Flowdock IssueTracker Irker Jenkins Jira MockCi Packagist - PipelinesEmail Pivotaltracker Redmine Teamcity Youtrack + Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat IssueTracker Irker + Jenkins Jira Mattermost MicrosoftTeams MockCi Packagist PipelinesEmail Pivotaltracker + Redmine Slack Teamcity UnifyCircuit Youtrack WebexTeams )).freeze def cast(value) diff --git a/lib/gitlab/slash_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb index b8affb423726b56fb3097d8c8ee1a8ea0c3fed3a..d28b5fb509a482d7d61596f6dc9e4a67df7aba88 100644 --- a/lib/gitlab/slash_commands/presenters/base.rb +++ b/lib/gitlab/slash_commands/presenters/base.rb @@ -63,7 +63,7 @@ def format_response(response) # Convert Markdown to slacks format def format(string) - Slack::Messenger::Util::LinkFormatter.format(string) + ::Slack::Messenger::Util::LinkFormatter.format(string) end def resource_url diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb index 7fb959a149c76e8228bb1a9725c2a6e073c85d82..a5c1f788c6837414420b5e173f01f0264d637979 100644 --- a/lib/mattermost/client.rb +++ b/lib/mattermost/client.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Mattermost - ClientError = Class.new(Mattermost::Error) + ClientError = Class.new(::Mattermost::Error) class Client attr_reader :user @@ -11,7 +11,7 @@ def initialize(user) end def with_session(&blk) - Mattermost::Session.new(user).with_session(&blk) + ::Mattermost::Session.new(user).with_session(&blk) end private @@ -52,12 +52,12 @@ def json_response(response) json_response = Gitlab::Json.parse(response.body, legacy_mode: true) unless response.success? - raise Mattermost::ClientError, json_response['message'] || 'Undefined error' + raise ::Mattermost::ClientError, json_response['message'] || 'Undefined error' end json_response rescue JSON::JSONError - raise Mattermost::ClientError, 'Cannot parse response' + raise ::Mattermost::ClientError, 'Cannot parse response' end end end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 523d82f91618a55d2eaca391ce8ba24749190244..9374c5c8f8f335fc879aae6f6a8a0efa32f06021 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module Mattermost - class NoSessionError < Mattermost::Error + class NoSessionError < ::Mattermost::Error def message 'No session could be set up, is Mattermost configured with Single Sign On?' 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 # instance with SSO configured where this GitLab instance is the provider. @@ -42,7 +42,7 @@ def with_session yield self rescue Errno::ECONNREFUSED => e Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n")) - raise Mattermost::NoSessionError + raise ::Mattermost::NoSessionError ensure destroy end @@ -100,11 +100,11 @@ def build_options(options) end def create - raise Mattermost::NoSessionError unless oauth_uri - raise Mattermost::NoSessionError unless token_uri + raise ::Mattermost::NoSessionError unless oauth_uri + raise ::Mattermost::NoSessionError unless token_uri @token = request_token - raise Mattermost::NoSessionError unless @token + raise ::Mattermost::NoSessionError unless @token @headers = { Authorization: "Bearer #{@token}" @@ -174,9 +174,9 @@ def lease_try_obtain def handle_exceptions yield rescue Gitlab::HTTP::Error => e - raise Mattermost::ConnectionError, e.message + raise ::Mattermost::ConnectionError, e.message rescue Errno::ECONNREFUSED => e - raise Mattermost::ConnectionError, e.message + raise ::Mattermost::ConnectionError, e.message end def parse_cookie(response) diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb index 39005f56dcb5ef799747e79ea1274cb81975d73c..299e3eeb953906fcfa3285f0d2afc9fab50cff30 100644 --- a/lib/microsoft_teams/notifier.rb +++ b/lib/microsoft_teams/notifier.rb @@ -32,7 +32,7 @@ def body(title: nil, summary: nil, attachments: nil, activity:) result['title'] = title result['summary'] = summary - result['sections'] << MicrosoftTeams::Activity.new(**activity).prepare + result['sections'] << ::MicrosoftTeams::Activity.new(**activity).prepare unless attachments.blank? result['sections'] << { text: attachments } diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 10bcee28f719f0f4b4cf83b605d15fceff0801d7..019ae19bf72ec95eefa8a8a42f96688f06857a02 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -53,7 +53,7 @@ context 'the request is succesull' 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') end end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 642c76abe79faab496cfc764bc8d3647b4c12bf1..f8474ab1082fc4aeccade430188f01c47cc1033e 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -47,7 +47,7 @@ let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') } 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 @@ -145,7 +145,7 @@ def do_put end 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 diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 77f73fa4d76414abcd761a122fc9eb27b4ef8b7b..e8ecc247dc56db8025703a271d93fb6d52d6ee1d 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -160,7 +160,7 @@ password { 'my-secret-password' } end - factory :slack_service do + factory :slack_service, class: 'Integrations::Slack' do project active { true } webhook { 'https://slack.service.url' } diff --git a/spec/features/projects/services/user_activates_slack_notifications_spec.rb b/spec/features/projects/services/user_activates_slack_notifications_spec.rb index 0cba1ee1c4cf5dab50b5ed50e755f5b4af744170..dec83ff1489305f97e351b552cc75fb7b0b17ab5 100644 --- a/spec/features/projects/services/user_activates_slack_notifications_spec.rb +++ b/spec/features/projects/services/user_activates_slack_notifications_spec.rb @@ -20,7 +20,7 @@ end context 'when service is already configured' do - let(:service) { SlackService.new } + let(:service) { Integrations::Slack.new } let(:project) { create(:project, slack_service: service) } before do diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb index 32755d1103c9fc2a788b20f93dd2febdedc9492f..5d57a226baf051181001aa90f2369ea28644d7b6 100644 --- a/spec/lib/mattermost/client_spec.rb +++ b/spec/lib/mattermost/client_spec.rb @@ -14,13 +14,13 @@ it 'yields an error on malformed JSON' do 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 it 'shows a client error if the request was unsuccessful' do 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 diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb index 0f2711e0b11c046749067779c5442deefb3b8be9..18cd1ff97a60638976568df1942e19ae9781a3bc 100644 --- a/spec/lib/mattermost/command_spec.rb +++ b/spec/lib/mattermost/command_spec.rb @@ -6,10 +6,10 @@ let(:params) { { 'token' => 'token', team_id: 'abc' } } before do - session = Mattermost::Session.new(nil) + session = ::Mattermost::Session.new(nil) 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) end @@ -57,7 +57,7 @@ end 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 diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index 67ccb48e3a72a784bf3f4dc646df2bff7c6a36a6..e2e1b4c28c79183c962165863f1a417ebd925e85 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -33,7 +33,7 @@ context 'without 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 @@ -49,7 +49,7 @@ it 'can not create a session' do expect do subject.with_session - end.to raise_error(Mattermost::NoSessionError) + end.to raise_error(::Mattermost::NoSessionError) end end @@ -113,13 +113,13 @@ expect_to_cancel_exclusive_lease(lease_key, 'uuid') # 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 it 'returns a NoSessionError error without lease' do 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 diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb index e3ef5ff5377e786e423fd5acf9dbadc9a437bb6a..b2db770c9b9457ab410fac66236f03bd1e3d5061 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -7,7 +7,7 @@ session = Mattermost::Session.new(nil) 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) end @@ -65,7 +65,7 @@ end 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 @@ -123,7 +123,7 @@ end 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 @@ -169,7 +169,7 @@ end 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 diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index f9860a911c2c5253517bd599abbfbdbc9667c5ba..7233f6093b707ad181b1d9783509e7002a61835b 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -672,7 +672,7 @@ expect(described_class.service_name_to_model('asana')).to eq(Integrations::Asana) # TODO We can remove this test when all models have been namespaced: # 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 it 'raises an error if service name is invalid' do diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/integrations/base_chat_notification_spec.rb similarity index 99% rename from spec/models/project_services/chat_notification_service_spec.rb rename to spec/models/integrations/base_chat_notification_spec.rb index 192b1df33b5a10274e0d38d908592bc47252357c..656eaa3bbddd0d8dd2824ad1d843262d7c6592dc 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/integrations/base_chat_notification_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ChatNotificationService do +RSpec.describe Integrations::BaseChatNotification do describe 'Associations' do before do allow(subject).to receive(:activated?).and_return(true) diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/integrations/discord_spec.rb similarity index 95% rename from spec/models/project_services/discord_service_spec.rb rename to spec/models/integrations/discord_spec.rb index ffe0a36dcdc61c7c88b0321a2803bc52926aeb8f..bff6a8ee5b20b0d29daeba751417b31c85b8c536 100644 --- a/spec/models/project_services/discord_service_spec.rb +++ b/spec/models/integrations/discord_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" -RSpec.describe DiscordService do - it_behaves_like "chat service", "Discord notifications" do +RSpec.describe Integrations::Discord do + it_behaves_like "chat integration", "Discord notifications" do let(:client) { Discordrb::Webhooks::Client } let(:client_arguments) { { url: webhook_url } } let(:payload) do diff --git a/spec/models/project_services/hangouts_chat_service_spec.rb b/spec/models/integrations/hangouts_chat_spec.rb similarity index 68% rename from spec/models/project_services/hangouts_chat_service_spec.rb rename to spec/models/integrations/hangouts_chat_spec.rb index 9d3bd457fc874dac61265f58f95c601ea1e0f021..17b40c484f5908cc0bf57cfa90dfa3e8b0dd1bcf 100644 --- a/spec/models/project_services/hangouts_chat_service_spec.rb +++ b/spec/models/integrations/hangouts_chat_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" -RSpec.describe HangoutsChatService do - it_behaves_like "chat service", "Hangouts Chat" do +RSpec.describe Integrations::HangoutsChat do + it_behaves_like "chat integration", "Hangouts Chat" do let(:client) { HangoutsChat::Sender } let(:client_arguments) { webhook_url } let(:payload) do diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/integrations/mattermost_spec.rb similarity index 74% rename from spec/models/project_services/mattermost_service_spec.rb rename to spec/models/integrations/mattermost_spec.rb index af1944ea77d24df96def724f8ff6fa2bb7642086..f3e7446897a3806d549e559f58594dd92eef5d10 100644 --- a/spec/models/project_services/mattermost_service_spec.rb +++ b/spec/models/integrations/mattermost_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe MattermostService do +RSpec.describe Integrations::Mattermost do it_behaves_like "slack or mattermost notifications", "Mattermost" end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/integrations/microsoft_teams_spec.rb similarity index 98% rename from spec/models/project_services/microsoft_teams_service_spec.rb rename to spec/models/integrations/microsoft_teams_spec.rb index 5f3a94a5b99f5ad0eaeceacdddc6b5b75a619333..2f1be233eb284056ec14a5d105f7b3c787925eb1 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/integrations/microsoft_teams_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MicrosoftTeamsService do +RSpec.describe Integrations::MicrosoftTeams do let(:chat_service) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com/' } @@ -64,7 +64,7 @@ end 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) end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/integrations/slack_spec.rb similarity index 99% rename from spec/models/project_services/slack_service_spec.rb rename to spec/models/integrations/slack_spec.rb index 2e2c1c666d9524adcb6faf18120e8ab9ca48a800..2c2e7f03003f83461aba46ae1c713ff94020f316 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/integrations/slack_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SlackService do +RSpec.describe Integrations::Slack do it_behaves_like "slack or mattermost notifications", 'Slack' describe '#execute' do diff --git a/spec/models/project_services/unify_circuit_service_spec.rb b/spec/models/integrations/unify_circuit_spec.rb similarity index 69% rename from spec/models/project_services/unify_circuit_service_spec.rb rename to spec/models/integrations/unify_circuit_spec.rb index 0c749322e079f189e05e0b56a90ee9ab7eeebe78..7a91b2d3c11436818b53d6bc356423c586e475c5 100644 --- a/spec/models/project_services/unify_circuit_service_spec.rb +++ b/spec/models/integrations/unify_circuit_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" -RSpec.describe UnifyCircuitService do - it_behaves_like "chat service", "Unify Circuit" do +RSpec.describe Integrations::UnifyCircuit do + it_behaves_like "chat integration", "Unify Circuit" do let(:client_arguments) { webhook_url } let(:payload) do { diff --git a/spec/models/project_services/webex_teams_service_spec.rb b/spec/models/integrations/webex_teams_spec.rb similarity index 64% rename from spec/models/project_services/webex_teams_service_spec.rb rename to spec/models/integrations/webex_teams_spec.rb index ed63f5bc48c312af668b5681847089d10dd539ea..b5cba6762aada9905c47bd52e02fb14917ed2cd3 100644 --- a/spec/models/project_services/webex_teams_service_spec.rb +++ b/spec/models/integrations/webex_teams_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" -RSpec.describe WebexTeamsService do - it_behaves_like "chat service", "Webex Teams" do +RSpec.describe Integrations::WebexTeams do + it_behaves_like "chat integration", "Webex Teams" do let(:client_arguments) { webhook_url } let(:payload) do { diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 87befdd4303f72d5be5a98b0dcf764346e4284cb..bbe985bfe0c27164f058bc6de8c7f8e925540468 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -11,10 +11,10 @@ let(:user) { create(:user) } before do - session = Mattermost::Session.new(nil) + session = ::Mattermost::Session.new(nil) 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) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c47ab3b797c49d238e3318c648e5bbdab66eeb64..d1d42892beca1fa090f5dbbdbdb2cbdc097ca585 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5250,7 +5250,7 @@ def enable_lfs it 'executes services with the specified scope' do 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 end @@ -5258,7 +5258,7 @@ def enable_lfs end 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| expect(instance).not_to receive(:async_execute) end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index dca5497de06f00978fad3ee41b553816cfb266f0..b59ee894fe838059c4e752c5e739ed390438a79b 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -140,7 +140,7 @@ end 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' }) expect { subject }.to change { ChatTeam.count }.from(0).to(1) diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index a5fce315d91def4e42dada19c55241e21f000eab..c794acdf76da78923c28c7b1a20d0d16942173e8 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -41,7 +41,7 @@ def destroy_group(group, user, async) let!(:chat_team) { create(:chat_team, namespace: group) } 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) end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb similarity index 69% rename from spec/support/shared_examples/models/chat_service_shared_examples.rb rename to spec/support/shared_examples/models/chat_integration_shared_examples.rb index 4a47aad0957f4162678a7911606c5e020e2babea..12809556ad4d97a30e5db730a3685b2ad27236e9 100644 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples "chat service" do |service_name| +RSpec.shared_examples "chat integration" do |integration_name| 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 + context "when integration is active" do before do subject.active = true end @@ -16,7 +16,7 @@ it_behaves_like "issue tracker service URL attribute", :webhook end - context "when service is inactive" do + context "when integration is inactive" do before do subject.active = false end @@ -47,12 +47,13 @@ WebMock.stub_request(:post, webhook_url) end - shared_examples "triggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "triggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "calls #{service_name} API" do + it "calls #{integration_name} API" do + # binding.pry result = subject.execute(sample_data) expect(result).to be(true) @@ -63,12 +64,12 @@ end end - shared_examples "untriggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "untriggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be(false) @@ -81,7 +82,7 @@ Gitlab::DataBuilder::Push.build_sample(project, user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" it "specifies the webhook when it is configured", if: defined?(client) do expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) @@ -95,19 +96,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -121,19 +122,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -143,19 +144,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end @@ -168,7 +169,7 @@ service.hook_data(issue, "open") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge events" do @@ -191,7 +192,7 @@ project.add_developer(user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with wiki page events" do @@ -207,7 +208,7 @@ let(:wiki_page) { create(:wiki_page, wiki: project.wiki, **opts) } let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with note events" do @@ -222,7 +223,7 @@ note: "a comment on a commit") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge request comment" do @@ -230,7 +231,7 @@ create(:note_on_merge_request, project: project, note: "merge request note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with issue comment" do @@ -238,7 +239,7 @@ create(:note_on_issue, project: project, note: "issue note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with snippet comment" do @@ -246,7 +247,7 @@ create(:note_on_project_snippet, project: project, note: "snippet note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -262,14 +263,14 @@ context "with failed pipeline" do let(:status) { "failed" } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with succeeded pipeline" do let(:status) { "success" } context "with default notify_only_broken_pipelines" do - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be_falsy @@ -281,7 +282,7 @@ subject.notify_only_broken_pipelines = false end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -291,19 +292,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -317,19 +318,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -339,19 +340,19 @@ end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end diff --git a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb index 09b7d1be704410f58e48f814dfefdff6f45d2f8c..ddcf693db7e4f2ecebc96db2c3c79d77e5b99bed 100644 --- a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb @@ -81,7 +81,7 @@ def execute_with_options(options) shared_examples 'calls the service API with the event message' do |event_message| 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 end @@ -95,7 +95,7 @@ def execute_with_options(options) let(:chat_service_params) { { username: 'slack_username' } } 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 end @@ -110,7 +110,7 @@ def execute_with_options(options) let(:chat_service_params) { { push_channel: 'random' } } 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 end @@ -136,7 +136,7 @@ def execute_with_options(options) let(:chat_service_params) { { issue_channel: 'random' } } 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 end @@ -147,7 +147,7 @@ def execute_with_options(options) end 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 end @@ -156,7 +156,7 @@ def execute_with_options(options) let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } } 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 end @@ -175,7 +175,7 @@ def execute_with_options(options) let(:chat_service_params) { { merge_request_channel: 'random' } } 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 end @@ -192,7 +192,7 @@ def execute_with_options(options) let(:chat_service_params) { { wiki_page_channel: 'random' } } 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 end @@ -216,7 +216,7 @@ def execute_with_options(options) let(:chat_service_params) { { note_channel: 'random' } } 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 end @@ -227,7 +227,7 @@ def execute_with_options(options) end 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 end @@ -236,7 +236,7 @@ def execute_with_options(options) let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } } 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 end