# rubocop:disable Style/CaseEquality # rubocop:disable Style/MultilineTernaryOperator # rubocop:disable Style/NestedTernaryOperator module Fastlane module Actions class SlackAction < Action def self.is_supported?(platform) true end # As there is a text limit in the notifications, we are # usually interested in the last part of the message # e.g. for tests def self.trim_message(message) # We want the last 7000 characters, instead of the first 7000, as the error is at the bottom start_index = [message.length - 7000, 0].max message = message[start_index..-1] message end def self.run(options) require 'slack-notifier' options[:message] = self.trim_message(options[:message].to_s || '') options[:message] = Slack::Notifier::LinkFormatter.format(options[:message]) notifier = Slack::Notifier.new(options[:slack_url]) if options[:username].to_s.length > 0 notifier.username = options[:username] else notifier.username = 'fastlane' end if options[:channel].to_s.length > 0 notifier.channel = options[:channel] notifier.channel = ('#' + notifier.channel) unless ['#', '@'].include?(notifier.channel[0]) # send message to channel by default end slack_attachment = generate_slack_attachments(options) return [notifier, slack_attachment] if Helper.is_test? # tests will verify the slack attachments and other properties if options[:icon_url].to_s.length > 0 result = notifier.ping '', icon_url: options[:icon_url], attachments: [slack_attachment] else result = notifier.ping '', icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png', attachments: [slack_attachment] end if result.code.to_i == 200 Helper.log.info 'Successfully sent Slack notification'.green else Helper.log.debug result raise 'Error pushing Slack message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile.'.red end end def self.description "Send a success/error message to your Slack group" end def self.available_options [ FastlaneCore::ConfigItem.new(key: :username, env_name: "FL_SLACK_USERNAME", description: "The username that should be displayed on Slack", optional: true), FastlaneCore::ConfigItem.new(key: :icon_url, env_name: "FL_SLACK_ICON_URL", description: "The URL of the icon that should be displayed on Slack", optional: true), FastlaneCore::ConfigItem.new(key: :message, env_name: "FL_SLACK_MESSAGE", description: "The message that should be displayed on Slack. This supports the standard Slack markup language", optional: true), FastlaneCore::ConfigItem.new(key: :channel, env_name: "FL_SLACK_CHANNEL", description: "#channel or @username", optional: true), FastlaneCore::ConfigItem.new(key: :slack_url, env_name: "SLACK_URL", description: "Create an Incoming WebHook for your Slack group", verify_block: proc do |value| raise "Invalid URL, must start with https://" unless value.start_with? "https://" end), FastlaneCore::ConfigItem.new(key: :payload, env_name: "FL_SLACK_PAYLOAD", description: "Add additional information to this post. payload must be a hash containg any key with any value", default_value: {}, is_string: false), FastlaneCore::ConfigItem.new(key: :default_payloads, env_name: "FL_SLACK_DEFAULT_PAYLOADS", description: "Remove some of the default payloads. More information about the available payloads on GitHub", optional: true, is_string: false), FastlaneCore::ConfigItem.new(key: :attachment_properties, env_name: "FL_SLACK_ATTACHMENT_PROPERTIES", description: "Merge additional properties in the slack attachment, see https://api.slack.com/docs/attachments", default_value: {}, is_string: false), FastlaneCore::ConfigItem.new(key: :success, env_name: "FL_SLACK_SUCCESS", description: "Was this build successful? (true/false)", optional: true, default_value: true, is_string: false) ] end def self.author "KrauseFx" end ##################################################### # @!group Helper ##################################################### def self.generate_slack_attachments(options) color = (options[:success] ? 'good' : 'danger') should_add_payload = ->(payload_name) { options[:default_payloads].nil? || options[:default_payloads].include?(payload_name) } slack_attachment = { fallback: options[:message], text: options[:message], color: color, mrkdwn_in: ["pretext", "text", "fields", "message"], fields: [] } # custom user payloads slack_attachment[:fields] += options[:payload].map do |k, v| { title: k.to_s, value: Slack::Notifier::LinkFormatter.format(v.to_s), short: false } end # lane if should_add_payload[:lane] slack_attachment[:fields] << { title: 'Lane', value: Actions.lane_context[Actions::SharedValues::LANE_NAME], short: true } end # test_result if should_add_payload[:test_result] slack_attachment[:fields] << { title: 'Result', value: (options[:success] ? 'Success' : 'Error'), short: true } end # git branch if Actions.git_branch && should_add_payload[:git_branch] slack_attachment[:fields] << { title: 'Git Branch', value: Actions.git_branch, short: true } end # git_author if Actions.git_author_email && should_add_payload[:git_author] if ENV['FASTLANE_SLACK_HIDE_AUTHOR_ON_SUCCESS'] && options[:success] # We only show the git author if the build failed else slack_attachment[:fields] << { title: 'Git Author', value: Actions.git_author_email, short: true } end end # last_git_commit if Actions.last_git_commit_message && should_add_payload[:last_git_commit] slack_attachment[:fields] << { title: 'Git Commit', value: Actions.last_git_commit_message, short: false } end # merge additional properties deep_merge(slack_attachment, options[:attachment_properties]) end # Adapted from http://stackoverflow.com/a/30225093/158525 def self.deep_merge(a, b) merger = proc do |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 end a.merge(b, &merger) end end end end # rubocop:enable Style/CaseEquality # rubocop:enable Style/MultilineTernaryOperator # rubocop:enable Style/NestedTernaryOperator