lib/lazylead/cc.rb
# frozen_string_literal: true

# The MIT License
#
# Copyright (c) 2019-2022 Yurii Dubinka
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom
# the Software is  furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
require "forwardable"

module Lazylead
  # Entry point for email CC detection.
  # The email may need CC email addresses, thus, there are various strategies
  #  how it can be done.
  class CC
    # Build an CC in order to detect email addresses by different conditions.
    #
    # Supported conditions(types):
    #  - PlainCC
    #  - PredefinedCC
    #  - ComponentCC
    #  - Empty
    #
    def detect(emails, sys)
      return emails if recognized?(emails)
      return PlainCC.new(emails) if plain?(emails)
      return EmptyCC.new if undefined?(emails)
      type = emails["type"].constantize
      return ComponentCC.new(emails["project"], sys) if type.is_a? ComponentCC
      type.new(emails["opts"])
    end

    # Detect that raw CC is a string which may has plain email addresses
    def plain?(text)
      (text.is_a? String) && text.to_s.include?("@")
    end

    def recognized?(emails)
      [EmptyCC, PlainCC, ComponentCC, PredefinedCC].member? emails.class
    end

    def undefined?(emails)
      return true unless emails.key? "type"
      emails["type"].nil? || emails["type"].blank?
    end
  end

  # Array of CC addresses from text for email notification.
  #
  #   PlainCC.new("a@f.com, , -,b@f.com").cc    # ==> ["a@f.com", "b@f.com"]
  #
  # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
  # License:: MIT
  class PlainCC
    include Enumerable
    extend Forwardable
    def_delegators :@cc, :each

    # The regexp expression for email notification is very simple, here is the
    # reason why https://bit.ly/38iLKeo
    def initialize(text, regxp = /[^\s]@[^\s]/)
      @text = text
      @regxp = regxp
    end

    def cc
      @cc ||= if @text.include? ","
                @text.split(",").map(&:strip).select { |e| e[@regxp] }
              elsif @text[@regxp]
                [@text.strip]
              end
    end

    def each(&block)
      cc.each(&block)
    end
  end

  # Empty CC email addresses.
  class EmptyCC
    def cc
      []
    end
  end

  # Predefined CC addresses for email notification.
  # You may define a hash where
  #  - key is Jira ticket component
  #  - value is CC email address(es)
  #
  # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
  # License:: MIT
  class PredefinedCC
    def initialize(orig)
      @orig = orig
    end

    def [](key)
      to_h[key]
    end

    def cc(*names)
      return to_h.values.flatten.uniq.compact if names.count.zero?
      return self[names.first] if names.count == 1
      to_h.values_at(names.first, *names.drop(1)).flatten.uniq.compact
    end

    def to_h
      @to_h ||= if @orig.is_a? Hash
                  @orig.each_with_object({}) do |i, o|
                    o[i.first] = Lazylead::PlainCC.new(i.last).cc
                  end
                else
                  {}
                end
    end
  end

  # CC addresses based on Jira component owners for email notification.
  # Allows to detect the CC for particular ticket based on its component.
  #
  # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
  # License:: MIT
  class ComponentCC < Lazylead::PredefinedCC
    def initialize(prj, jira)
      super({})
      @prj = prj
      @jira = jira
    end

    def to_h
      @to_h ||= components.each_with_object({}) do |c, h|
        email = lead(c.attrs["id"])
        next if email.nil? || email.blank?
        h[c.attrs["name"]] = email
      end
    end

    private

    def lead(component_id)
      @jira.raw do |j|
        lead = j.Component
                .find(component_id, expand: "", fields: "")
                .attrs["lead"]
        next if lead.nil? || lead.empty?
        j.User.find(lead["key"]).attrs["emailAddress"]
      end
    end

    def components
      @jira.raw do |j|
        j.Project.find(@prj, expand: "components", fields: "").components
      end
    end
  end
end
