Change SlackService to SlackNotificationsService
This commit is contained in:
parent
141faaacf9
commit
a5ccaded65
19 changed files with 223 additions and 168 deletions
|
@ -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
|
||||||
|
|
147
app/models/project_services/chat_notification_service.rb
Normal file
147
app/models/project_services/chat_notification_service.rb
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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' },
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -220,8 +220,8 @@ class Service < ActiveRecord::Base
|
||||||
pivotaltracker
|
pivotaltracker
|
||||||
pushover
|
pushover
|
||||||
redmine
|
redmine
|
||||||
mattermost
|
mattermost_notification
|
||||||
slack
|
slack_notification
|
||||||
teamcity
|
teamcity
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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) }
|
||||||
|
|
Loading…
Reference in a new issue