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