# frozen_string_literal: true require "cgi" require "date" require "jira-ruby" class IssueNotFoundError < StandardError; end module Groundskeeper # Wraps an interface to Jira. class Jira JIRA_USERNAME_KEY = "JIRA_USERNAME" JIRA_API_TOKEN_KEY = "JIRA_API_TOKEN" JIRA_SITE_KEY = "JIRA_SITE" DEPLOY_TO_STAGING = "Deploy to Staging" DEPLOY_TO_PRODUCTION = "Deploy to Production" TRANSITION_IDS = { DEPLOY_TO_STAGING => 201, DEPLOY_TO_PRODUCTION => 221 }.freeze attr_reader :client, :prefix ISSUE_PATTERN = "%s-\\d+" # Wraps the jira-ruby Client class. class JiraClient attr_reader :client def initialize @client = JIRA::Client.new( username: ENV[JIRA_USERNAME_KEY], password: ENV[JIRA_API_TOKEN_KEY], site: ENV[JIRA_SITE_KEY], context_path: "", auth_type: :basic, read_timeout: 120 ) end # :nocov: def create_version(name:, prefix:) client.Version .build .save(name: name, project: prefix) end # :nocov: # :nocov: def add_version_to_issue(issue_id:, version_name:) raise IssueNotFoundError if client.Issue.find(issue_id).nil? client.Issue .find(issue_id) .save(update: { fixVersions: [{ add: { name: version_name } }] }) end # :nocov: # :nocov: def transition_issue(issue_id:, transition_id:) client.Issue .find(issue_id) .transitions .build .save(transition: { id: transition_id }) end # :nocov: # :nocov: def fetch_issues_by_fix_version(version:) search_path = "/rest/api/2/search" query = "fixVersion=\"#{version}\"" request_url = "#{search_path}?fields=key&jql=#{CGI.escape(query)}" response = client.get(request_url).body JSON.parse(response)["issues"].map { |issue| issue["key"] } rescue JIRA::HTTPError puts "Jira request failed - #{request_url}" end # :nocov: # :nocov: def release_version(project:, version:) version_name = "#{project.repo_name} #{version}" client.Project .find(project.jira_prefix) .versions .find { |element| element.name == version_name } .save(released: true) end # :nocov: end def self.build(prefix) new( prefix: prefix, client: JiraClient.new ) end def initialize(client: nil, prefix: nil) @prefix = prefix @client = client end def credentials? ENV[JIRA_USERNAME_KEY].present? && ENV[JIRA_API_TOKEN_KEY].present? && ENV[JIRA_SITE_KEY].present? end # Returns the list of Jira issues found in a set of commit messages. def included_issues(changes) issue_expression = /#{format(ISSUE_PATTERN, prefix)}/ changes .map { |change| change.scan(issue_expression) } .flatten .compact .sort end def create_remote_version(name) client.create_version(name: name, prefix: prefix) end # rubocop:disable Metrics/MethodLength def add_version_to_remote_issues(name, issue_ids) failed_issues = [] issue_ids.each do |issue_id| begin client.add_version_to_issue(issue_id: issue_id, version_name: name) rescue IssueNotFoundError failed_issues << issue_id next end end return "" unless failed_issues.count.positive? "Jira issue(s) not found, is #{failed_issues.join(', ')} correct?" end # rubocop:enable Metrics/MethodLength def transition_remote_issues(transition_type, issue_ids) issue_ids.each do |issue_id| client.transition_issue( issue_id: issue_id, transition_id: TRANSITION_IDS[transition_type] ) end end # :nocov: def fetch_issues_by_fix_version(version) client.fetch_issues_by_fix_version(version: version) end # :nocov: def release_version(project, version) client.release_version(project: project, version: version) end end end