Change SlackService to SlackNotificationsService

This commit is contained in:
Felipe Artur 2016-12-06 15:59:03 -02:00
parent 141faaacf9
commit a5ccaded65
19 changed files with 223 additions and 168 deletions

View file

@ -95,8 +95,8 @@ class Project < ActiveRecord::Base
has_one :asana_service, dependent: :destroy has_one :asana_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy
has_one :mattermost_slash_commands_service, dependent: :destroy has_one :mattermost_slash_commands_service, dependent: :destroy
has_one :mattermost_service, dependent: :destroy has_one :mattermost_notification_service, dependent: :destroy
has_one :slack_service, dependent: :destroy has_one :slack_notification_service, dependent: :destroy
has_one :buildkite_service, dependent: :destroy has_one :buildkite_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy

View file

@ -0,0 +1,147 @@
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Service
include ChatMessage
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
# Custom serialized properties initialization
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
self.notify_only_broken_pipelines = true
end
end
def can_test?
valid?
end
def supported_events
%w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page]
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
object_kind = data[:object_kind]
data = data.merge(
project_url: project_url,
project_name: project_name
)
# WebHook events often have an 'update' event that follows a 'open' or
# 'close' action. Ignore update events for now to prevent duplicate
# messages from arriving.
message = get_message(object_kind, data)
return false unless message
opt = {}
opt[:channel] = get_channel_field(object_kind).presence || channel || default_channel
opt[:username] = username if username
notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
true
end
def event_channel_names
supported_events.map { |event| event_channel_name(event) }
end
def event_field(event)
fields.find { |field| field[:name] == event_channel_name(event) }
end
def global_fields
fields.reject { |field| field[:name].end_with?('channel') }
end
def default_channel
raise NotImplementedError
end
private
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
PushMessage.new(data)
when "issue"
IssueMessage.new(data) unless is_update?(data)
when "merge_request"
MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
WikiPageMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name)
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel }
end
end
def event_channel_name(event)
"#{event}_channel"
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
def project_url
project.web_url
end
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end
end

View file

@ -1,148 +1,21 @@
# Base class for Chat services # Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from. # This class is not meant to be used directly, but only to inherit from.
class ChatService < Service class ChatService < Service
include ChatMessage
default_value_for :category, 'chat' default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel has_many :chat_names, foreign_key: :service_id
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
validates :webhook, presence: true, url: true, if: :activated? def valid_token?(token)
self.respond_to?(:token) &&
def initialize_properties self.token.present? &&
# Custom serialized properties initialization ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
self.notify_only_broken_pipelines = true
end
end
def can_test?
valid?
end end
def supported_events def supported_events
%w[push issue confidential_issue merge_request note tag_push []
build pipeline wiki_page]
end end
def execute(data) def trigger(params)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
object_kind = data[:object_kind]
data = data.merge(
project_url: project_url,
project_name: project_name
)
# WebHook events often have an 'update' event that follows a 'open' or
# 'close' action. Ignore update events for now to prevent duplicate
# messages from arriving.
message = get_message(object_kind, data)
return false unless message
opt = {}
opt[:channel] = get_channel_field(object_kind).presence || channel || default_channel
opt[:username] = username if username
notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
true
end
def event_channel_names
supported_events.map { |event| event_channel_name(event) }
end
def event_field(event)
fields.find { |field| field[:name] == event_channel_name(event) }
end
def global_fields
fields.reject { |field| field[:name].end_with?('channel') }
end
def default_channel
raise NotImplementedError raise NotImplementedError
end end
private
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
PushMessage.new(data)
when "issue"
IssueMessage.new(data) unless is_update?(data)
when "merge_request"
MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
WikiPageMessage.new(data)
end
end
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name)
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: default_channel }
end
end
def event_channel_name(event)
"#{event}_channel"
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
def project_url
project.web_url
end
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
when 'failed'
true
else
false
end
end
end end

View file

@ -1,4 +1,4 @@
class MattermostService < ChatService class MattermostNotificationService < ChatNotificationService
def title def title
'Mattermost notifications' 'Mattermost notifications'
end end
@ -8,7 +8,7 @@ class MattermostService < ChatService
end end
def to_param def to_param
'mattermost' 'mattermost_notification'
end end
def help def help
@ -28,7 +28,7 @@ class MattermostService < ChatService
def default_fields def default_fields
[ [
{ type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' }, { type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'username' },
{ type: 'checkbox', name: 'notify_only_broken_builds' }, { type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' }, { type: 'checkbox', name: 'notify_only_broken_pipelines' },

View file

@ -1,18 +1,8 @@
class MattermostSlashCommandsService < Service class MattermostSlashCommandsService < ChatService
include TriggersHelper include TriggersHelper
prop_accessor :token prop_accessor :token
def valid_token?(token)
self.respond_to?(:token) &&
self.token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
def supported_events
[]
end
def can_test? def can_test?
false false
end end

View file

@ -1,4 +1,4 @@
class SlackService < ChatService class SlackNotificationService < ChatNotificationService
def title def title
'Slack notifications' 'Slack notifications'
end end
@ -8,7 +8,7 @@ class SlackService < ChatService
end end
def to_param def to_param
'slack' 'slack_notification'
end end
def help def help

View file

@ -220,8 +220,8 @@ class Service < ActiveRecord::Base
pivotaltracker pivotaltracker
pushover pushover
redmine redmine
mattermost mattermost_notification
slack slack_notification
teamcity teamcity
] ]
end end

View file

@ -1,7 +1,11 @@
# rubocop:disable all # rubocop:disable all
class MoveSlackServiceToWebhook < ActiveRecord::Migration class MoveSlackServiceToWebhook < ActiveRecord::Migration
DOWNTIME = true
DOWNTIME_REASON = 'Move old fields "token" and "subdomain" to one single field "webhook"'
def change def change
SlackService.all.each do |slack_service| SlackNotificationService.all.each do |slack_service|
if ["token", "subdomain"].all? { |property| slack_service.properties.key? property } if ["token", "subdomain"].all? { |property| slack_service.properties.key? property }
token = slack_service.properties['token'] token = slack_service.properties['token']
subdomain = slack_service.properties['subdomain'] subdomain = slack_service.properties['subdomain']

View file

@ -0,0 +1,14 @@
class ChangeSlackServiceToSlackNotificationService < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = 'Rename SlackService to SlackNotificationService'
def up
execute("UPDATE services SET type = 'SlackNotificationService' WHERE type = 'SlackService'")
end
def down
execute("UPDATE services SET type = 'SlackService' WHERE type = 'SlackNotificationService'")
end
end

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161212142807) do ActiveRecord::Schema.define(version: 20161213172958) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"

View file

@ -473,7 +473,7 @@ module API
desc: 'The description of the tracker' desc: 'The description of the tracker'
} }
], ],
'slack' => [ 'slack-notification' => [
{ {
required: true, required: true,
name: :webhook, name: :webhook,
@ -493,6 +493,14 @@ module API
desc: 'The channel name' desc: 'The channel name'
} }
], ],
'mattermost-notification' => [
{
required: true,
name: :webhook,
type: String,
desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...'
}
],
'teamcity' => [ 'teamcity' => [
{ {
required: true, required: true,

View file

@ -2,8 +2,8 @@ require 'spec_helper'
feature 'Projects > Slack service > Setup events', feature: true do feature 'Projects > Slack service > Setup events', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:service) { SlackService.new } let(:service) { SlackNotificationService.new }
let(:project) { create(:project, slack_service: service) } let(:project) { create(:project, slack_notification_service: service) }
background do background do
service.fields service.fields

View file

@ -136,8 +136,8 @@ project:
- assembla_service - assembla_service
- asana_service - asana_service
- gemnasium_service - gemnasium_service
- slack_service - slack_notification_service
- mattermost_service - mattermost_notification_service
- buildkite_service - buildkite_service
- bamboo_service - bamboo_service
- teamcity_service - teamcity_service

View file

@ -0,0 +1,12 @@
require 'spec_helper'
describe ChatNotificationService, models: true do
describe "Associations" do
before do
allow(subject).to receive(:activated?).and_return(true)
end
it { is_expected.to validate_presence_of :webhook }
end
end

View file

@ -2,7 +2,14 @@ require 'spec_helper'
describe ChatService, models: true do describe ChatService, models: true do
describe "Associations" do describe "Associations" do
before { allow(subject).to receive(:activated?).and_return(true) } it { is_expected.to have_many :chat_names }
it { is_expected.to validate_presence_of :webhook } end
describe '#valid_token?' do
subject { described_class.new }
it 'is false as it has no token' do
expect(subject.valid_token?('wer')).to be_falsey
end
end end
end end

View file

@ -1,5 +1,5 @@
require 'spec_helper' require 'spec_helper'
describe SlackService, models: true do describe MattermostNotificationService, models: true do
it_behaves_like "slack or mattermost" it_behaves_like "slack or mattermost"
end end

View file

@ -1,5 +1,5 @@
require 'spec_helper' require 'spec_helper'
describe MattermostService, models: true do describe SlackNotificationService, models: true do
it_behaves_like "slack or mattermost" it_behaves_like "slack or mattermost"
end end

View file

@ -22,8 +22,8 @@ describe Project, models: true do
it { is_expected.to have_many(:protected_branches).dependent(:destroy) } it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
it { is_expected.to have_many(:chat_services) } it { is_expected.to have_many(:chat_services) }
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:slack_notification_service).dependent(:destroy) }
it { is_expected.to have_one(:mattermost_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) }
it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:boards).dependent(:destroy) } it { is_expected.to have_many(:boards).dependent(:destroy) }