Class: Tefoji::JiraApi

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/tefoji/jira_api.rb

Overview

An interface to send API request to Jira using the rest-client gem. Contained here are the bits of knowledge necessary to form API calls to Jira.

Direct Known Subclasses

JiraMockApi

Constant Summary collapse

FIELD_ACCOUNT_ID =

Jira field keys

'accountId'
FIELD_ID =
'id'
FIELD_KEY =
'key'
FIELD_NAME =
'name'
FIELD_VALUE =
'value'
ISSUE_EPIC =

Issue type constants

'Epic'
ISSUE_NEW_FEATURE =
'New Feature'
ISSUE_SUB_TASK =
'Sub-task'
ISSUE_TASK =
'Task'

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#fatal

Instance Attribute Details

#jira_base_rest_urlObject (readonly)

Returns the value of attribute jira_base_rest_url.



12
13
14
# File 'lib/tefoji/jira_api.rb', line 12

def jira_base_rest_url
  @jira_base_rest_url
end

#jira_base_urlObject (readonly)

Returns the value of attribute jira_base_url.



12
13
14
# File 'lib/tefoji/jira_api.rb', line 12

def jira_base_url
  @jira_base_url
end

#jira_usernameObject (readonly)

Returns the value of attribute jira_username.



12
13
14
# File 'lib/tefoji/jira_api.rb', line 12

def jira_username
  @jira_username
end

#log_levelObject

Returns the value of attribute log_level.



12
13
14
# File 'lib/tefoji/jira_api.rb', line 12

def log_level
  @log_level
end

#loggerObject

Returns the value of attribute logger.



13
14
15
# File 'lib/tefoji/jira_api.rb', line 13

def logger
  @logger
end

Instance Method Details

#add_issue_to_epic(epic_issue, issue_to_add) ⇒ Object

Use greehopper to add an issue to an epic. See the comments around the #greenhopper_put method for more explanation.



137
138
139
140
141
142
143
144
145
146
# File 'lib/tefoji/jira_api.rb', line 137

def add_issue_to_epic(epic_issue, issue_to_add)
  epic_key = epic_issue[FIELD_KEY]
  issue_key = issue_to_add[FIELD_KEY]
  request_path = "epics/#{epic_key}/add"
  request_data = {
    'ignoreEpics' => true,
    'issueKeys' => [issue_key]
  }
  greenhopper_put(request_path, request_data)
end

#add_watcher(issue_key, watcher_name) ⇒ Object



173
174
175
176
177
# File 'lib/tefoji/jira_api.rb', line 173

def add_watcher(issue_key, watcher_name)
  request_path = "issue/#{issue_key}/watchers"
  watcher_id = (watcher_name)
  jira_post(request_path, watcher_id)
end

#authenticate(jira_base_url, requested_user = nil, jira_auth = nil) ⇒ Object

Depending on how user specified their authorization we want to derive and store the username and the Base64-encoded ‘username:application-token’ string.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/tefoji/jira_api.rb', line 39

def authenticate(jira_base_url, requested_user = nil, jira_auth = nil)
  @jira_base_url = jira_base_url
  @jira_base_rest_url = "#{jira_base_url}/rest/api/2"

  @logger.info "Using Jira instance: \"#{jira_base_url}\""

  # If we found an auth string, try to use it. Allow the requested_user
  # to override and send us to prompt
  decoded_auth = nil
  unless jira_auth.nil?
    auth_user, auth_application_token = Base64.decode64(jira_auth).split(':')
    # Only set decoded auth if the user name in the auth string matches the
    # requested user name
    unless requested_user && requested_user != auth_user
      decoded_auth = [auth_user, auth_application_token]
    end
  end

  # If there was no auth string or the requested user didn't match the auth string
  # prompt for details as needed.
  if decoded_auth.nil?
    decoded_auth = []
    decoded_auth[0] = requested_user
    if requested_user.nil?
      print 'Enter jira user name: '
      decoded_auth[0] = $stdin.gets.chomp
    end
    decoded_auth[1] = IO.console.getpass(
      "Enter jira application_token for #{decoded_auth[0]}: "
    )
  end

  @jira_username = decoded_auth[0]

  # HTTP doesn't like linefeeds in the auth string, hence #strict_encode64
  @jira_auth = Base64.strict_encode64(decoded_auth.join(':'))
  @authentication_header = { 'Authorization' => "Basic #{@jira_auth}" }
end

#create_issue(issue_data) ⇒ Object

Create a Jira issue



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/tefoji/jira_api.rb', line 117

def create_issue(issue_data)
  request_path = 'issue'
  jira_fields = issue_data_to_jira_fields(issue_data)
  jira_post(request_path, jira_fields)
rescue RestClient::Forbidden
  fatal "Forbidden: could not send #{request_path} request to Jira. " \
        'Jira may not be accepting requests.'
rescue RestClient::BadRequest => e
  error_json = JSON.parse(e.response.body)
  errors = error_json['errors'].values.join("\n")
  fatal "Bad Request: something went wrong with this #{request_path} request: " \
        "#{request_path}, #{jira_fields}\n" \
        "Errors were: #{errors}"
rescue RestClient::Unauthorized
  fatal "Unauthorized: could not send #{request_path} request to Jira. " \
        'Did you use the correct credentials?'
end

#get_components(project) ⇒ Object

Get information about user in Jira



94
95
96
97
# File 'lib/tefoji/jira_api.rb', line 94

def get_components(project)
  search_parameters = "project/#{project}/components"
  jira_get(search_parameters)
end

#get_transition_id(issue_key, status) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/tefoji/jira_api.rb', line 211

def get_transition_id(issue_key, status)
  transitions_request_path = "issue/#{issue_key}/transitions"
  response = jira_get(transitions_request_path)

  fatal "Transitions data not found for issue: \"#{issue_key}\"" if response.empty?

  downcased_status = status.downcase

  transitions_data = response['transitions']
  transition = transitions_data.find { |transition_data| transition_data['name'].downcase == downcased_status }

  if transition.nil?
    warn "No transition ID found for issue: \"#{issue_key}\" status: \"#{status}\"\n \
          available transitions: #{transitions_data}"
    return nil
  end

  transition_id = transition['id']

  @logger.debug("issue: \"#{issue_key}\" status: \"#{status}\"" \
                "converted to transition ID: \"#{transition_id}\"")

  return transition_id.to_i
end

#get_username(username, fail_if_not_found = true) ⇒ Object

Get information about user in Jira



88
89
90
91
# File 'lib/tefoji/jira_api.rb', line 88

def get_username(username, fail_if_not_found = true)
  search_parameters = "user/search?query=#{username}"
  jira_get(search_parameters, fail_if_not_found)
end


152
153
154
155
156
157
158
159
160
# File 'lib/tefoji/jira_api.rb', line 152

def link_issues(inward_issue, outward_issue, type = 'Blocks')
  request_path = 'issueLink'
  request_data = {
    'type' => { 'name' => type },
    'inwardIssue' => { 'key'  => inward_issue },
    'outwardIssue' => { 'key' => outward_issue }
  }
  jira_post(request_path, request_data)
end

#retrieve_issue(issue_url) ⇒ Object



148
149
150
# File 'lib/tefoji/jira_api.rb', line 148

def retrieve_issue(issue_url)
  jira_get(issue_url)
end

#save_authentication(save_file_name) ⇒ Object

Save authentication YAML to the a file for reuse.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/tefoji/jira_api.rb', line 100

def save_authentication(save_file_name)
  backup_file_name = "#{save_file_name}.bak"

  if File.readable?(save_file_name)
    FileUtils.cp(save_file_name, backup_file_name)
    @logger.info "Saved #{save_file_name} to #{backup_file_name}"
  end

  jira_auth = "jira-auth: #{@jira_auth}\n"
  File.write(save_file_name, jira_auth)

  File.chmod(0o600, save_file_name)

  @logger.info "Saved Jira authentication to #{save_file_name}"
end

#test_authenticationObject

Do this so we can inform the user quickly that their credentials didn’t work There may be a better test, but this is the one the original Winston uses



80
81
82
83
84
85
# File 'lib/tefoji/jira_api.rb', line 80

def test_authentication
  get_username(@jira_username)
rescue RestClient::Forbidden
  fatal 'Forbidden: either the authentication is incorrect or ' \
        'Jira might be requiring a CAPTCHA response from the web interface.'
end

#transition(issue_key, status) ⇒ Object

Used to set issue items related to transitions. Right now, only used for ‘status’ transitions.



164
165
166
167
168
169
170
# File 'lib/tefoji/jira_api.rb', line 164

def transition(issue_key, status)
  request_path = "issue/#{issue_key}/transitions"
  request_data = {
    'transition' => { 'id' => status }
  }
  jira_post(request_path, request_data)
end

#user_email_to_account_id(user_email) ⇒ Object

jira cloud api calls require an accountId rather than a username. This method will convert emails or display names to accountIds.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/tefoji/jira_api.rb', line 196

def (user_email)
   = (user_email)
  response = get_username()

  fatal "Jira account ID not found for \"#{}\"" if response.empty?
   = response[0]['accountId']

  @logger.debug("user account name: \"#{} \" " \
                "converted to account ID: \"#{}\"")

  return  if 

  fatal "Jira account ID not found for #{}"
end

#user_name_to_account_name(user_name) ⇒ Object

Jira on-prem used user names (‘eric.griswold’, ‘tefoji’, etc.) to identify users. Jira cloud requires an ‘@domain.extension’ suffix. Handle translating from the onprem form to the cloud form. Nothe the special case of the tefoji service user.



183
184
185
186
187
188
189
190
191
192
# File 'lib/tefoji/jira_api.rb', line 183

def (user_name)
  case user_name
  when 'tefoji'
    '[email protected]'
  when %r{URI::MailTo::EMAIL_REGEXP}
    user_name
  else
    "#{user_name.gsub(%r{@.*}, '')}@perforce.com"
  end
end