require 'json' module RoboPigeon::Jira class Ticket MAX_TRANSITIONS = 10 ISSUE_PATH = '/rest/api/2/issue'.freeze attr_accessor :ticket, :project, :issue_type, :summary, :fields, :assignee, :duedate, :reporter, :description def initialize(ticket = nil) self.ticket = ticket self.fields = {} attempt_update_from_server self.project = ticket.split('-')[0] if ticket end def attempt_update_from_server return unless ticket get = jira_request.get("#{ISSUE_PATH}/#{ticket}") raise "Failed to look up issue #{ticket}" unless get.status < 400 raw = JSON.parse(get.body) self.project = raw['fields']['project']['key'] self.issue_type = raw['fields']['issuetype']['name'] self.summary = raw['fields']['summary'] self.reporter = raw['fields']['reporter']['name'] if raw['fields']['reporter'] self.assignee = raw['fields']['assignee']['name'] if raw['fields']['assignee'] end def jira_request RoboPigeon::Jira::Client.jira_request end def create! response = jira_request.post do |req| req.url '/rest/api/2/issue' req.body = { fields: { project: { key: project }, issuetype: { name: issue_type }, summary: summary, description: description, assignee: { name: reporter } }.merge(fields) }.to_json end begin self.ticket = JSON.parse(response.body).fetch('key') RoboPigeon::Jira::Client.last_created_ticket = ticket File.write('last_created_jira_ticket', ticket) rescue KeyError raise RoboPigeon::Jira::RequiredFieldNotSet, "Response from JIRA did not contain new issue key: #{response.body}" end end def assign(user_email) user_data = jira_request.get("/rest/api/2/user/search?username=#{user_email}") user = JSON.parse(user_data.body).first raise "Unable to find jira user with email #{user_email}" unless user self.assignee = user['name'] self.reporter ||= user['name'] if ticket jira_request.post do |req| req.url "/rest/api/3/issue/#{ticket}/assignee" req.body = { name: assignee }.to_json end end end def set_reporter(user_email) raise 'Cannot modify reporter' if ticket user_data = jira_request.get("/rest/api/2/user/search?username=#{user_email}") user = JSON.parse(user_data.body).first raise "Unable to find jira user with email #{user_email}" unless user self.reporter = user['name'] end def set_field(name, value) require_field(:issue_type) request_path = "#{ISSUE_PATH}/createmeta?projectKeys=#{project}&expand=projects.issuetypes.fields" response = jira_request.get(request_path) raw = JSON.parse(response.body) fields = raw['projects'].first['issuetypes'].select do |it| it['name'] == issue_type end.first['fields'] field = fields.keys.select do |key| fields[key]['name'] == name end.first raise "Field #{name} was not found" unless field self.fields[field] = if fields[field]['schema']['type'] == 'array' array_of(value, fields[field]) elsif fields[field]['allowedValues'].nil? value else { value: value } end if ticket post = jira_request.put do |req| req.url "#{ISSUE_PATH}/#{ticket}" req.body = { fields: self.fields }.to_json end end raise 'Failed to update field' unless post.nil? || post.status < 400 end def set_issuetype(name) require_field(:project) request_path = "#{ISSUE_PATH}/createmeta?projectKeys=#{project}&expand=projects.issuetypes.fields" response = jira_request.get(request_path) raw = JSON.parse(response.body) types = raw['projects'].first['issuetypes'].map { |type| type['name'] } raise RoboPigeon::Jira::FieldDoesNotConform, "Issue type #{name} was not found in #{types.join(', ')}" unless types.include?(name) self.issue_type = name end def perform_transition(transition) require_ticket get_fields = jira_request.get("#{ISSUE_PATH}/#{ticket}?expand=transitions.fields") transition_details = JSON.parse(get_fields.body)['transitions'].find do |trans| trans['name'].casecmp(transition).zero? end raise "Unable to find valid transition '#{transition}'" unless transition_details jira_request.post do |req| req.url "#{ISSUE_PATH}/#{ticket}/transitions" req.body = { transition: { id: transition_details['id'] } }.to_json end end def current_state require_ticket get = jira_request.get do |req| req.url "#{ISSUE_PATH}/#{ticket}" end JSON.parse(get.body)['fields']['status']['name'] end def wait_for_state!(state, timeout = RoboPigeon::Jira::Client.wait_for_state_timeout, time = Time.now, sleep_time = 15) require_ticket waited = (Time.now - time).to_i minutes = waited / 60 seconds = waited % 60 if current_state == state puts "#{ticket} in #{state} after #{minutes} minutes #{seconds} seconds" return end raise RoboPigeon::Jira::WaitTimeout, "Timed out waiting for #{ticket} to transition to #{state} after #{minutes} minutes #{seconds} seconds" if (timeout.to_i - waited) <= 0 puts "Still waiting for #{ticket} in #{state}... waited #{minutes} minutes #{seconds} seconds" sleep sleep_time wait_for_state!(state, timeout, time, sleep_time) end def add_comment(comment) require_ticket post = jira_request.post do |req| req.url "#{ISSUE_PATH}/#{ticket}/comment" req.body = { body: comment }.to_json end raise 'Failed to create comment' unless post.status == 201 end private def require_ticket raise RoboPigeon::Jira::TicketNotFoundOrSet, 'Ticket was not set!' if ticket.nil? end def require_field(field) raise RoboPigeon::Jira::RequiredFieldNotSet, "You must set a #{field}" if send(field).nil? end def array_of(values, field_metadata) raise TypeError('provided Jira field must be an array') unless field_metadata['schema']['type'] == 'array' values.split(',').map do |value| value.strip! if field_metadata['schema']['items'] == 'string' value else { name: value } end end end end end