Class: Apadmi::Grout::JiraBoardService

Inherits:
BoardService show all
Defined in:
lib/apadmi/grout/service/board_service/jira_board_service.rb

Overview

Provides a layer of abstraction on top of the Jira api

Instance Method Summary collapse

Constructor Details

#initialize(jira_config, network_service) ⇒ JiraBoardService

Returns a new instance of JiraBoardService.

Parameters:



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 11

def initialize(jira_config, network_service)
  @options = {
    username: jira_config.username,
    password: jira_config.api_token,
    site: jira_config.base_url,
    context_path: jira_config.context_path,
    auth_type: :basic
  }
  @project = jira_config.project_key
  @network_service = network_service
  @jira_client = JIRA::Client.new(@options)
end

Instance Method Details

#add_comment(key, comment) ⇒ Object

Parameters:

  • key (String)
  • comment (String)


97
98
99
100
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 97

def add_comment(key, comment)
  payload = "{\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"#{comment}\"}"
  @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
end

Adds a related work link to a Jira version, skipping if a link with the same URL already exists.

Parameters:

  • version_name (String)
  • url (String)
  • title (String)


263
264
265
266
267
268
269
270
271
272
273
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 263

def add_version_related_work(version_name, url, title)
  version = create_or_get_versions([version_name]).find { |v| v.name == version_name }
  raise "Version '#{version_name}' not found" if version.nil?

  response = @network_service.do_get("/rest/api/3/version/#{version.id}/relatedwork")
  existing_links = JSON.parse(response.body) || []
  return if existing_links.any? { |link| link["url"] == url }

  payload = { category: "link", url: url, title: title }.to_json
  @network_service.do_post("/rest/api/3/version/#{version.id}/relatedwork", payload)
end

#all_versionsArray<JIRA::Resource::Version>

Returns:

  • (Array<JIRA::Resource::Version>)


189
190
191
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 189

def all_versions
  @jira_client.Project.find(@project).versions
end

#assign_fixversion_to_tickets(issues, version_names) ⇒ Object

Assigns one or more fix versions to all given issues that are missing them. Filters in-memory from the already-fetched issue objects — no extra network calls. Each issue gets a single PUT containing only the versions it is missing.

Parameters:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 213

def assign_fixversion_to_tickets(issues, version_names)
  return if issues.empty? || version_names.empty?

  versions = create_or_get_versions(version_names)

  issues.each do |issue|
    if issue.raw_object.nil?
      warn "Skipping fix version assignment for unclassified fallback issue #{issue.key}"
      next
    end

    existing_ids = issue.raw_object.fixVersions.map(&:id)
    missing = versions.reject { |v| existing_ids.include?(v.id) }
    next if missing.empty?

    adds = missing.map { |v| "{\"add\": {\"id\": \"#{v.id}\"}}" }.join(", ")
    @network_service.do_put("/rest/api/2/issue/#{issue.key}", "{\"update\": {\"fixVersions\": [#{adds}]}}")
  end
end

#assign_fixversions(key, version_strings) ⇒ Object

Parameters:

  • key (String)
  • version_strings (Array<String>)


235
236
237
238
239
240
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 235

def assign_fixversions(key, version_strings)
  versions = create_or_get_versions(version_strings)
  fixversions = versions.map { |v| "{\"id\": \"#{v.id}\"}" }.join(", ")
  payload = "{\"fields\" : {\"fixVersions\": [#{fixversions}] }}"
  @network_service.do_put("/rest/api/2/issue/#{key}", payload)
end

#create_version(release_date, name) ⇒ JIRA::Resource::Version

Parameters:

  • release_date (String)
  • name (String)

Returns:

  • (JIRA::Resource::Version)


170
171
172
173
174
175
176
177
178
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 170

def create_version(release_date, name)
  version = @jira_client.Version.build
  version.save!({
                  project: @project,
                  name: name,
                  startDate: release_date
                })
  version
end

#delete_version(version_id, move_version_id) ⇒ Object

Parameters:

  • version_id (int)
  • move_version_id (int)


182
183
184
185
186
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 182

def delete_version(version_id, move_version_id)
  payload = "{\"moveFixIssuesTo\": #{move_version_id}, \"moveAffectedIssuesTo\": #{move_version_id}, " \
            "\"customFieldReplacementList\": []}"
  @network_service.do_post("/rest/api/2/version/#{version_id}/removeAndSwap", payload)
end

#find_issues_by_keys(keys) ⇒ Array<Apadmi::Grout::Issue>

Parameters:

  • keys (String[])

Returns:



26
27
28
29
30
31
32
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 26

def find_issues_by_keys(keys)
  return [] if keys.length <= 0

  jql_search = "project = '#{@project}' AND issue IN (#{keys.join(", ")})"
  issues = @jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq
  convert(issues)
end

#flag_ticket(key, comment) ⇒ Object

Parameters:

  • key (String)
  • comment (String)


74
75
76
77
78
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 74

def flag_ticket(key, comment)
  payload = "{\"flag\":true,\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"#{comment}\"}"
  @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
  sleep 2 # Seems to take a while for flagging and unflagging to register
end

#flagged?(key) ⇒ Boolean

Returns bool.

Parameters:

  • key (String)

Returns:

  • (Boolean)

    bool



89
90
91
92
93
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 89

def flagged?(key)
  jql_search = "project = '#{@project}' AND issue IN (#{key}) AND Flagged is not EMPTY"
  response = @jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq
  response != []
end

#get_ticket_fixversions(key) ⇒ Array<String>

Parameters:

  • key (String)

Returns:

  • (Array<String>)


254
255
256
257
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 254

def get_ticket_fixversions(key)
  response = @network_service.do_get("/rest/api/2/issue/#{key}")
  JSON.parse(response.body)["fields"]["fixVersions"].map { |v| v["name"] } || []
end

#get_ticket_prs(issue) ⇒ Array<PullRequest>

Parameters:

Returns:



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 139

def get_ticket_prs(issue)
  if issue.raw_object.nil?
    warn "Skipping PR lookup for unclassified fallback issue #{issue.key}"
    return []
  end

  query_string = "issueId=#{issue.raw_object.id}&applicationType=bitbucket&dataType=pullrequest"
  response = @network_service.do_get("/rest/dev-status/latest/issue/details?#{query_string}")

  parsed_detail = JSON.parse(response.body)["detail"]

  return [] if parsed_detail.empty?
  return [] unless parsed_detail[0].key?("pullRequests")

  parsed_detail[0]["pullRequests"].map do |pr|
    PullRequest.new(pr["id"], pr["name"], pr["status"].to_s == "MERGED", pr["status"].to_s == "DECLINED")
  end
end

#get_ticket_status(key) ⇒ String

Parameters:

  • key (String)

Returns:

  • (String)


132
133
134
135
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 132

def get_ticket_status(key)
  issue = @jira_client.Issue.find(key)
  issue.status.name
end

#get_ticket_subtask(keys, component = nil) ⇒ Array<Apadmi::Grout::Issue>

Parameters:

  • keys (String)
  • component (String) (defaults to: nil)

Returns:



161
162
163
164
165
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 161

def get_ticket_subtask(keys, component = nil)
  jql_search = "project = '#{@project}' AND parent IN #{keys.to_s.gsub("[", "(").gsub("]", ")")}" \
                + (component.nil? ? "" : " AND component IN ('#{component}')")
  convert(@jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq)
end

#get_tickets_by_component(component_key) ⇒ Array<Apadmi::Grout::Issue>

Parameters:

  • component_key (String)

Returns:



119
120
121
122
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 119

def get_tickets_by_component(component_key)
  jql_search = "project = '#{@project}' AND component IN ('#{component_key}')"
  convert(@jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq)
end

#get_top_comment(key) ⇒ JIRA::Resource::Comment

Parameters:

  • key (String)

Returns:

  • (JIRA::Resource::Comment)


104
105
106
107
108
109
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 104

def get_top_comment(key)
  jql_search = "project = '#{@project}' AND issue IN (#{key})"
  response = @jira_client.Issue.jql(jql_search, { fields: ["comment"], max_results: 1000 }).uniq
  comments = response[0].comments.reverse
  comments.empty? ? nil : comments[0]
end

#get_transitions(key) ⇒ Object

Parameters:

  • key (String)


125
126
127
128
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 125

def get_transitions(key)
  issue = @jira_client.Issue.find(key)
  @jira_client.Transition.all(issue: issue)
end

#remove_comment(key, comment_id) ⇒ Object

Parameters:

  • key (String)
  • comment_id (String)


113
114
115
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 113

def remove_comment(key, comment_id)
  @network_service.do_delete("/rest/api/2/issue/#{key}/comment/#{comment_id}")
end

#remove_fixversions(key, version_names) ⇒ Object

Removes specific fix versions from a ticket using the update.remove operation.

Parameters:

  • key (String)
  • version_names (Array<String>)


245
246
247
248
249
250
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 245

def remove_fixversions(key, version_names)
  return if version_names.empty?

  removes = version_names.map { |name| "{\"remove\": {\"name\": \"#{name}\"}}" }.join(", ")
  @network_service.do_put("/rest/api/2/issue/#{key}", "{\"update\": {\"fixVersions\": [#{removes}]}}")
end

#search_unblocked_issues(component, status, ticket_types = [], options = nil) ⇒ Array<Apadmi::Grout::Issue>

Parameters:

Returns:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 39

def search_unblocked_issues(component, status, ticket_types = [], options = nil)
  allow_no_sprint = options&.include_no_sprint_tickets || false
  component_filter = (" AND component = '#{component}' " unless component.empty?) || ""
  status_filter = (" AND status = '#{status}' " unless status.empty?) || ""
  type_filter = ("AND (#{ticket_types.map { |type| "type = #{type}" }.join("OR ")})" unless ticket_types.empty?) || ""
  empty_sprint_condition = ("OR sprint is EMPTY" if allow_no_sprint) || ""

  jql_search = %{
    project = '#{@project}'
    #{status_filter} #{component_filter} #{type_filter}
    AND (sprint in openSprints() #{empty_sprint_condition}) AND (labels not in(Blocked) or labels is EMPTY)
    AND Flagged is EMPTY
  }

  issues = @jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq
  convert(issues)
end

#transition_issue(issue, state_name) ⇒ Object

Parameters:



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 59

def transition_issue(issue, state_name)
  if issue.raw_object.nil?
    warn "Skipping transition for unclassified fallback issue #{issue.key}"
    return
  end

  transitions = @jira_client.Transition.all(issue: issue.raw_object)
  transition = transitions.find { |elem| elem.name.downcase == state_name.downcase }

  trans = issue.raw_object.transitions.build
  trans.save!("transition" => { "id" => transition.id })
end

#un_flag_ticket(key) ⇒ Object

Parameters:

  • key (String)


81
82
83
84
85
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 81

def un_flag_ticket(key)
  payload = "{\"flag\":false,\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"Unflagged by CI\"}"
  @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
  sleep 2 # Seems to take a while for flagging and unflagging to register
end

#update_version(version_name, released: nil, description: nil) ⇒ Object

Parameters:

  • version_name (String)
  • released (Boolean, nil) (defaults to: nil)

    omit to leave unchanged

  • description (String, nil) (defaults to: nil)

    omit to leave unchanged

Raises:

  • (ArgumentError)


196
197
198
199
200
201
202
203
204
205
206
# File 'lib/apadmi/grout/service/board_service/jira_board_service.rb', line 196

def update_version(version_name, released: nil, description: nil)
  raise ArgumentError, "Must provide at least one of: released, description" if released.nil? && description.nil?

  version = create_or_get_versions([version_name]).first
  raise "Version '#{version_name}' not found" if version.nil?

  fields = {}
  fields[:released] = released unless released.nil?
  fields[:description] = description unless description.nil?
  @network_service.do_put("/rest/api/2/version/#{version.id}", fields.to_json)
end