class Danger::RequestSources::VSTS

Attributes

pr_json[RW]

Public Class Methods

env_vars() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 10
def self.env_vars
  [
    "DANGER_VSTS_API_TOKEN",
    "DANGER_VSTS_HOST"
  ]
end
new(ci_source, environment) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 23
def initialize(ci_source, environment)
  self.ci_source = ci_source

  @is_vsts_ci = environment.key? "DANGER_VSTS_HOST"

  @api = VSTSAPI.new(ci_source.repo_slug, ci_source.pull_request_id, environment)
end
optional_env_vars() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 17
def self.optional_env_vars
  [
    "DANGER_VSTS_API_VERSION"
  ]
end

Public Instance Methods

client() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 43
def client
  @api
end
fetch_details() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 51
def fetch_details
  self.pr_json = @api.fetch_pr_json
end
host() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 47
def host
  @host ||= @api.host
end
messages_are_equivalent(m1, m2) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 177
def messages_are_equivalent(m1, m2)
  blob_regexp = %r{blob/[0-9a-z]+/}
  m1.file == m2.file && m1.line == m2.line &&
    m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
end
organisation() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 71
def organisation
  nil
end
post_new_comment(comment) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 117
def post_new_comment(comment)
  @api.post_comment(comment)
end
scm() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 39
def scm
  @scm ||= GitRepo.new
end
setup_danger_branches() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 55
def setup_danger_branches
  base_branch = self.pr_json[:targetRefName].sub("refs/heads/", "")
  base_commit = self.pr_json[:lastMergeTargetCommit][:commitId]
  head_branch = self.pr_json[:sourceRefName].sub("refs/heads/", "")
  head_commit = self.pr_json[:lastMergeSourceCommit][:commitId]

  # Next, we want to ensure that we have a version of the current branch at a known location
  scm.ensure_commitish_exists_on_branch! base_branch, base_commit
  self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"

  # OK, so we want to ensure that we have a known head branch, this will always represent
  # the head of the PR ( e.g. the most recent commit that will be merged. )
  scm.ensure_commitish_exists_on_branch! head_branch, head_commit
  self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
end
submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger") click to toggle source
# File lib/danger/request_sources/vsts.rb, line 141
def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
  # Avoid doing any fetches if there's no inline comments
  return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty?

  pr_threads = @api.fetch_last_comments
  danger_threads = pr_threads.select do |thread|
    comment = thread[:comments].first
    comment_content = comment[:content].nil? ? "" : comment[:content]

    next comment_content.include?("generated_by_#{danger_id}")
  end
  non_danger_threads = pr_threads - danger_threads

  warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id)
  errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id)
  messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id)
  markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id)

  # submit removes from the array all comments that are still in force
  # so we strike out all remaining ones
  danger_threads.each do |thread|
    violation = violations_from_table(thread[:comments].first[:content]).first
    if !violation.nil? && violation.sticky
      body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
      @api.update_comment(thread[:id], thread[:comments].first[:id], body)
    end
  end

  {
    warnings: warnings,
    errors: errors,
    messages: messages,
    markdowns: markdowns
  }
end
submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger") click to toggle source
# File lib/danger/request_sources/vsts.rb, line 183
def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger")
  previous_violations ||= []
  is_markdown_content = kind == :markdown
  emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]

  messages.reject do |m|
    next false unless m.file && m.line

    # Once we know we're gonna submit it, we format it
    if is_markdown_content
      body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts")
    else
      # Hide the inline link behind a span
      m.message = m.message.gsub("\n", "<br />")
      m = process_markdown(m, true)
      body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts")
      # A comment might be in previous_violations because only now it's part of the unified diff
      # We remove from the array since it won't have a place in the table anymore
      previous_violations.reject! { |v| messages_are_equivalent(v, m) }
    end

    matching_threads = danger_threads.select do |comment_data|
      if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? &&
         comment_data[:threadContext][:filePath] == m.file &&
         comment_data[:threadContext].key?(:rightFileStart) &&
         comment_data[:threadContext][:rightFileStart][:line] == m.line
        # Parse it to avoid problems with strikethrough
        violation = violations_from_table(comment_data[:comments].first[:content]).first
        if violation
          messages_are_equivalent(violation, m)
        else
          blob_regexp = %r{blob/[0-9a-z]+/}
          comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "")
        end
      else
        false
      end
    end

    if matching_threads.empty?
      @api.post_inline_comment(body, m.file, m.line)

      # Not reject because this comment has not completed
      next false
    else
      # Remove the surviving comment so we don't strike it out
      danger_threads.reject! { |c| matching_threads.include? c }

      # Update the comment to remove the strikethrough if present
      thread = matching_threads.first
      @api.update_comment(thread[:id], thread[:comments].first[:id], body)
    end

    # Remove this element from the array
    next true
  end
end
update_old_comment(new_comment, danger_id: "danger") click to toggle source
# File lib/danger/request_sources/vsts.rb, line 121
def update_old_comment(new_comment, danger_id: "danger")
  comment_updated = false
  @api.fetch_last_comments.each do |c|
    thread_id = c[:id]
    comment = c[:comments].first
    comment_id = comment[:id]
    comment_content = comment[:content].nil? ? "" : comment[:content]
    # Skip the comment if it wasn't posted by danger
    next unless comment_content.include?("generated_by_#{danger_id}")
    # Skip the comment if it's an inline comment
    next unless c[:threadContext].nil?

    # Updated the danger posted comment
    @api.update_comment(thread_id, comment_id, new_comment)
    comment_updated = true
  end
  # If no comment was updated, post a new one
  post_new_comment(new_comment) unless comment_updated
end
update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 75
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
  unless @api.supports_comments?
    return
  end

  regular_violations = regular_violations_group(
    warnings: warnings,
    errors: errors,
    messages: messages,
    markdowns: markdowns
  )

  inline_violations = inline_violations_group(
    warnings: warnings,
    errors: errors,
    messages: messages,
    markdowns: markdowns
  )

  rest_inline_violations = submit_inline_comments!(**{
    danger_id: danger_id,
    previous_violations: {}
  }.merge(inline_violations))

  main_violations = merge_violations(
    regular_violations, rest_inline_violations
  )

  comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors])
  comment += "\n\n"
  comment += generate_comment(**{
    previous_violations: {},
    danger_id: danger_id,
    template: "vsts"
  }.merge(main_violations))
  if new_comment || remove_previous_comments
    post_new_comment(comment)
  else
    update_old_comment(comment, danger_id: danger_id)
  end
end
validates_as_api_source?() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 35
def validates_as_api_source?
  @api.credentials_given?
end
validates_as_ci?() click to toggle source
# File lib/danger/request_sources/vsts.rb, line 31
def validates_as_ci?
  @is_vsts_ci
end

Private Instance Methods

inline_violations_group(warnings: [], errors: [], messages: [], markdowns: []) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 252
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
  cmp = proc do |a, b|
    next -1 unless a.file && a.line
    next 1 unless b.file && b.line

    next a.line <=> b.line if a.file == b.file

    next a.file <=> b.file
  end

  # Sort to group inline comments by file
  {
    warnings: warnings.select(&:inline?).sort(&cmp),
    errors: errors.select(&:inline?).sort(&cmp),
    messages: messages.select(&:inline?).sort(&cmp),
    markdowns: markdowns.select(&:inline?).sort(&cmp)
  }
end
merge_violations(*violation_groups) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 271
def merge_violations(*violation_groups)
  violation_groups.inject({}) do |accumulator, group|
    accumulator.merge(group) { |_, old, fresh| old + fresh }
  end
end
regular_violations_group(warnings: [], errors: [], messages: [], markdowns: []) click to toggle source
# File lib/danger/request_sources/vsts.rb, line 243
def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
  {
    warnings: warnings.reject(&:inline?),
    errors: errors.reject(&:inline?),
    messages: messages.reject(&:inline?),
    markdowns: markdowns.reject(&:inline?)
  }
end