Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Ruby 3.0] replace slack-notifier gem with internal library #18537

Merged
merged 11 commits into from
Apr 30, 2021
1 change: 0 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ PATH
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
Expand Down
1 change: 0 additions & 1 deletion fastlane.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ Gem::Specification.new do |spec|
# spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = Dir["*/lib"]

spec.add_dependency('slack-notifier', '>= 2.0.0', '< 3.0.0') # Slack notifications
spec.add_dependency('xcodeproj', '>= 1.13.0', '< 2.0.0') # Modify Xcode projects
spec.add_dependency('xcpretty', '~> 0.3.0') # prettify xcodebuild output
spec.add_dependency('terminal-notifier', '>= 2.0.0', '< 3.0.0') # macOS notifications
Expand Down
37 changes: 19 additions & 18 deletions fastlane/lib/fastlane/actions/slack.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'fastlane/notification/slack'

# rubocop:disable Style/CaseEquality
# rubocop:disable Style/MultilineTernaryOperator
# rubocop:disable Style/NestedTernaryOperator
Expand All @@ -6,12 +8,12 @@ module Actions
class SlackAction < Action
class Runner
def initialize(slack_url)
@notifier = Slack::Notifier.new(slack_url)
@notifier = Fastlane::Notification::Slack.new(slack_url)
end

def run(options)
options[:message] = self.class.trim_message(options[:message].to_s || '')
options[:message] = Slack::Notifier::Util::LinkFormatter.format(options[:message])
options[:message] = Fastlane::Notification::Slack::LinkConverter.convert(options[:message])

options[:pretext] = options[:pretext].gsub('\n', "\n") unless options[:pretext].nil?

Expand All @@ -37,21 +39,21 @@ def run(options)
end

def post_message(channel:, username:, attachments:, link_names:, icon_url:, fail_on_error:)
results = @notifier.ping('', channel: channel, username: username, link_names: link_names, icon_url: icon_url, attachments: attachments)
rescue => exception
UI.error("Exception: #{exception}")
ensure
result = results.first if results
if !result.nil? && result.code.to_i == 200
UI.success('Successfully sent Slack notification')
@notifier.post_to_legacy_incoming_webhook(
channel: channel,
username: username,
link_names: link_names,
icon_url: icon_url,
attachments: attachments
)
UI.success('Successfully sent Slack notification')
rescue => error
UI.error("Exception: #{error}")
message = "Error pushing Slack message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile, this is usually caused by a misspelled or changed group/channel name or an expired SLACK_URL"
if fail_on_error
UI.user_error!(message)
else
UI.verbose(result) unless result.nil?
message = "Error pushing Slack message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile, this is usually caused by a misspelled or changed group/channel name or an expired SLACK_URL"
if fail_on_error
UI.user_error!(message)
else
UI.error(message)
end
UI.error(message)
end
end

Expand Down Expand Up @@ -84,7 +86,7 @@ def self.generate_slack_attachments(options)
slack_attachment[:fields] += options[:payload].map do |k, v|
{
title: k.to_s,
value: Slack::Notifier::Util::LinkFormatter.format(v.to_s),
value: Fastlane::Notification::Slack::LinkConverter.convert(v.to_s),
short: false
}
end
Expand Down Expand Up @@ -168,7 +170,6 @@ def self.is_supported?(platform)
end

def self.run(options)
require 'slack-notifier'
Runner.new(options[:slack_url]).run(options)
end

Expand Down
56 changes: 56 additions & 0 deletions fastlane/lib/fastlane/notification/slack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Fastlane
module Notification
Copy link
Member

@rogerluan rogerluan Apr 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost forgot to mention that I like this namespace strategy a lot 馃槉

class Slack
def initialize(webhook_url)
@webhook_url = webhook_url
@client = Faraday.new do |conn|
conn.use(Faraday::Response::RaiseError)
end
end

# Overriding channel, icon_url and username is only supported in legacy incoming webhook.
# Also note that the use of attachments has been discouraged by Slack, in favor of Block Kit.
# https://api.slack.com/legacy/custom-integrations/messaging/webhooks
def post_to_legacy_incoming_webhook(channel:, username:, attachments:, link_names:, icon_url:)
@client.post(@webhook_url) do |request|
request.headers['Content-Type'] = 'application/json'
request.body = {
channel: channel,
username: username,
icon_url: icon_url,
attachments: attachments,
link_names: link_names
}.to_json
end
end

# This class was inspired by `LinkFormatter` in `slack-notifier` gem
# https://github.com/stevenosloan/slack-notifier/blob/4bf6582663dc9e5070afe3fdc42d67c14a513354/lib/slack-notifier/util/link_formatter.rb
class LinkConverter
HTML_PATTERN = %r{<a.*?href=['"](?<link>#{URI.regexp})['"].*?>(?<label>.+?)<\/a>}
MARKDOWN_PATTERN = /\[(?<label>[^\[\]]*?)\]\((?<link>#{URI.regexp}|mailto:#{URI::MailTo::EMAIL_REGEXP})\)/

def self.convert(string)
convert_markdown_to_slack_link(convert_html_to_slack_link(string.scrub))
end

def self.convert_html_to_slack_link(string)
string.gsub(HTML_PATTERN) do |match|
slack_link(Regexp.last_match[:link], Regexp.last_match[:label])
end
end

def self.convert_markdown_to_slack_link(string)
string.gsub(MARKDOWN_PATTERN) do |match|
slack_link(Regexp.last_match[:link], Regexp.last_match[:label])
end
end

def self.slack_link(href, text)
return "<#{href}>" if text.nil? || text.empty?
"<#{href}|#{text}>"
end
end
end
end
end
28 changes: 28 additions & 0 deletions fastlane/spec/notification/slack_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
describe Fastlane::Notification::Slack do
describe Fastlane::Notification::Slack::LinkConverter do
it 'should convert HTML anchor tag to Slack link format' do
{
%|Hello <a href="https://fastlane.tools">fastlane</a>| => 'Hello <https://fastlane.tools|fastlane>',
%|Hello <a href='https://fastlane.tools'>fastlane</a>| => 'Hello <https://fastlane.tools|fastlane>',
%|Hello <a id="foo" href="https://fastlane.tools">fastlane</a>| => 'Hello <https://fastlane.tools|fastlane>',
%|Hello <a href="https://fastlane.tools">fastlane</a> <a href="https://github.com/fastlane">GitHub</a>| => 'Hello <https://fastlane.tools|fastlane> <https://github.com/fastlane|GitHub>'
}.each do |input, output|
expect(described_class.convert(input)).to eq(output)
end
end

it 'should convert Markdown link to Slack link format' do
{
%|Hello [fastlane](https://fastlane.tools)| => 'Hello <https://fastlane.tools|fastlane>',
%|Hello [fastlane](mailto:fastlane@fastlane.tools)| => 'Hello <mailto:fastlane@fastlane.tools|fastlane>',
%|Hello [fastlane](https://fastlane.tools) [GitHub](https://github.com/fastlane)| => 'Hello <https://fastlane.tools|fastlane> <https://github.com/fastlane|GitHub>',
%|Hello [[fastlane](https://fastlane.tools) in brackets]| => 'Hello [<https://fastlane.tools|fastlane> in brackets]',
%|Hello [](https://fastlane.tools)| => 'Hello <https://fastlane.tools>',
%|Hello ([fastlane](https://fastlane.tools) in parens)| => 'Hello (<https://fastlane.tools|fastlane> in parens)',
%|Hello ([fastlane(:rocket:)](https://fastlane.tools) in parens)| => 'Hello (<https://fastlane.tools|fastlane(:rocket:)> in parens)'
}.each do |input, output|
expect(described_class.convert(input)).to eq(output)
end
end
end
end