apadmi_grout 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_action.rb +24 -0
  4. data/lib/apadmi/grout/actions/find_tickets_to_move_action/find_tickets_to_move_ado_action.rb +91 -0
  5. data/lib/apadmi/grout/{jira/actions/find_tickets_to_move_action.rb → actions/find_tickets_to_move_action/find_tickets_to_move_jira_action.rb} +16 -17
  6. data/lib/apadmi/grout/actions/generate_release_notes_action/generate_release_notes_action.rb +48 -0
  7. data/lib/apadmi/grout/actions/generate_release_notes_action/issue_classifier.rb +51 -0
  8. data/lib/apadmi/grout/{release_notes/actions → actions}/issues_from_changelog_action.rb +11 -6
  9. data/lib/apadmi/grout/actions/move_tickets_action.rb +34 -0
  10. data/lib/apadmi/grout/di.rb +53 -9
  11. data/lib/apadmi/grout/{jira/models/find_tickets_options.rb → models/ado_config.rb} +2 -2
  12. data/lib/apadmi/grout/models/find_tickets_options.rb +44 -0
  13. data/lib/apadmi/grout/{jira/models → models}/flag_messages.rb +0 -0
  14. data/lib/apadmi/grout/models/issue.rb +49 -0
  15. data/lib/apadmi/grout/{jira/models → models}/pull_request.rb +0 -0
  16. data/lib/apadmi/grout/models/release_notes_config.rb +47 -0
  17. data/lib/apadmi/grout/{release_notes/models → models}/release_notes_templates.rb +6 -6
  18. data/lib/apadmi/grout/service/board_service/ado_board_service.rb +199 -0
  19. data/lib/apadmi/grout/service/board_service/board_service.rb +59 -0
  20. data/lib/apadmi/grout/{jira/wrapper/jira_wrapper.rb → service/board_service/jira_board_service.rb} +65 -107
  21. data/lib/apadmi/grout/utils/git_utils.rb +18 -0
  22. data/lib/apadmi/grout/utils/network_service.rb +88 -0
  23. data/lib/apadmi/grout/version.rb +1 -1
  24. data/lib/apadmi_grout.rb +3 -21
  25. metadata +20 -13
  26. data/lib/apadmi/grout/jira/actions/move_jira_tickets_action.rb +0 -58
  27. data/lib/apadmi/grout/jira/models/version.rb +0 -23
  28. data/lib/apadmi/grout/release_notes/actions/generate_release_notes_action.rb +0 -39
  29. data/lib/apadmi/grout/release_notes/models/release_notes_config.rb +0 -74
@@ -9,7 +9,7 @@ module Apadmi
9
9
 
10
10
  ### Version
11
11
  {{config.app_version}}
12
- {{config.hello}}
12
+
13
13
  ### Release date
14
14
  {{config.date}}
15
15
 
@@ -30,35 +30,35 @@ Commit Hash: {{config.commit_hash}}
30
30
  %{### New functionality
31
31
  {{# classified_issues.features.first}}
32
32
  {{# classified_issues.features}}
33
- * [{{ key }} - {{ summary }}]({{ base_url }}{{ key }})
33
+ * [{{ key }} - {{ summary }}]({{ url }})
34
34
  {{/ classified_issues.features}}
35
35
 
36
36
  {{/ classified_issues.features.first}}
37
37
  {{# classified_issues.improvements.first}}
38
38
  ### Improvements
39
39
  {{# classified_issues.improvements}}
40
- * [{{ key }} - {{ summary }}]({{ base_url }}{{ key }})
40
+ * [{{ key }} - {{ summary }}]({{ url }})
41
41
  {{/ classified_issues.improvements}}
42
42
 
43
43
  {{/ classified_issues.improvements.first}}
44
44
  {{# classified_issues.tasks.first}}
45
45
  ### Completed Tasks
46
46
  {{# classified_issues.tasks}}
47
- * [{{ key }} - {{ summary }}]({{ base_url }}{{ key }})
47
+ * [{{ key }} - {{ summary }}]({{ url }})
48
48
  {{/ classified_issues.tasks}}
49
49
 
50
50
  {{/ classified_issues.tasks.first}}
51
51
  {{# classified_issues.defects.first}}
52
52
  ### Fixed defects
53
53
  {{# classified_issues.defects}}
54
- * [{{ key }} - {{ summary }}]({{ base_url }}{{ key }})
54
+ * [{{ key }} - {{ summary }}]({{ url }})
55
55
  {{/ classified_issues.defects}}
56
56
 
57
57
  {{/ classified_issues.defects.first}}
58
58
  {{# classified_issues.others.first}}
59
59
  ### Other issue types
60
60
  {{# classified_issues.others}}
61
- * [{{ key }} - {{ summary }}]({{ base_url }}{{ key }})
61
+ * [{{issue_type}} - {{ key }} - {{ summary }}]({{ url }})
62
62
  {{/ classified_issues.others}}
63
63
 
64
64
  {{/ classified_issues.others.first}}}
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./board_service"
4
+
5
+ module Apadmi
6
+ module Grout
7
+ # Provides a layer of abstraction on top of the ADO api
8
+ class AdoBoardService < BoardService
9
+ API_VERSION_PARAM = "api-version=6.0"
10
+
11
+ # @param [Apadmi::Grout::NetworkService] network_service
12
+ # @param [Apadmi::Grout::AdoConfig] ado_config
13
+ # @param [Logger] logger
14
+ def initialize(network_service, ado_config, logger)
15
+ @network_service = network_service
16
+ @ado_config = ado_config
17
+ @logger = logger
18
+ end
19
+
20
+ # @param [String[]] keys
21
+ # @return [Array<Apadmi::Grout::Issue>]
22
+ def find_issues_by_keys(keys)
23
+ return [] if keys.length <= 0
24
+
25
+ keys = keys.map { |k| sanitise_key(k) } # Allows supporting raw keys, and keys prefixed with a hash which is common ADO practice
26
+
27
+ res = @network_service.do_get("/wit/workitems?ids=#{keys.join(",")}&api-version=7.1-preview.2")
28
+ parsed = JSON.parse(res.body)
29
+ items = parsed["value"]
30
+
31
+ return if items.nil? || items.empty?
32
+
33
+ items.map { |i| Apadmi::Grout::Issue.from_ado_hash(i) }
34
+ end
35
+
36
+ # @param component [String] Included to be consistent with JIRA, this will boil down to a tag in ADO
37
+ # @param status [String]
38
+ # @param ticket_types [String[]]
39
+ # @param options [Apadmi::Grout::AdoFindTicketsOptions] Additional options to filter by
40
+
41
+ # @return [Array<Apadmi::Grout::Issue>]
42
+ def search_unblocked_issues(component, status, ticket_types = [], options = nil)
43
+ tags = (options&.required_tags || []).push(*component).filter { |it| !it.blank? }
44
+ not_tags = (options&.not_tags || []).filter { |it| !it.blank? }
45
+
46
+ raise "Tags can either be required or not required, not both" unless (tags & not_tags).empty?
47
+
48
+ tags_filter = tags.map { |tag| " AND [System.Tags] CONTAINS '#{tag}' " }.join(" ")
49
+ not_tags_filter = not_tags.map { |tag| " AND [System.Tags] NOT CONTAINS '#{tag}'" }.join(" ")
50
+
51
+ status_filter = ("AND [System.State]='#{status}' " unless status.blank?) || ""
52
+ type_filter = ("AND [System.WorkItemType] IN (#{ticket_types.map { |t| "'#{t}'" }.join(", ")})" unless ticket_types.empty?) || ""
53
+ wiql = %(
54
+ Select [System.Id]
55
+ From WorkItems
56
+ Where [System.Id] >= 1
57
+ #{status_filter}
58
+ #{tags_filter}
59
+ #{not_tags_filter}
60
+ #{type_filter}
61
+ )
62
+
63
+ get_issues_by_wiql(wiql)
64
+ end
65
+
66
+ # @param [String] key
67
+ # @param [String] comment
68
+ def flag_ticket(key, comment)
69
+ key = sanitise_key(key)
70
+
71
+ add_tag(key, @ado_config.flag_tag)
72
+ add_comment(key, comment) unless comment.blank?
73
+ end
74
+
75
+ # @param [String] key
76
+ def un_flag_ticket(key)
77
+ key = sanitise_key(key)
78
+
79
+ remove_tag(key, @ado_config.flag_tag)
80
+ end
81
+
82
+ # @param [String] key
83
+ # @param [String] comment
84
+ def add_comment(key, comment)
85
+ key = sanitise_key(key)
86
+
87
+ payload = {
88
+ "text" => comment
89
+ }
90
+ @network_service.do_post("/wit/workitems/#{key}/comments?#{API_VERSION_PARAM}-preview.3", payload.to_json)
91
+ end
92
+
93
+ # @param [String] key
94
+ # @param [String] tag
95
+ def add_tag(key, tag)
96
+ payload = [{
97
+ "op" => "add",
98
+ "path" => "/fields/System.Tags",
99
+ "value" => tag
100
+ }]
101
+ @network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
102
+ end
103
+
104
+ # @param [String] key
105
+ # @param [String] tag
106
+ def remove_tag(key, tag)
107
+ current_tags = get_work_item(key)["fields"]["System.Tags"].split("; ")
108
+ new_tags = current_tags.filter { |t| t != tag }
109
+
110
+ payload = [{
111
+ "op" => "replace",
112
+ "path" => "/fields/System.Tags",
113
+ "value" => new_tags.join("; ")
114
+ }]
115
+ @network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
116
+ end
117
+
118
+ # @param [String] key
119
+ def flagged?(key)
120
+ current_tags = get_work_item(key)["fields"]["System.Tags"].split("; ")
121
+ current_tags.include?(@ado_config.flag_tag)
122
+ end
123
+
124
+ # @param [String] key
125
+ # @return [RawWorkItemHash]
126
+ def get_work_item(key)
127
+ key = sanitise_key(key)
128
+
129
+ res = @network_service.do_get("/wit/workitems/#{key}?#{API_VERSION_PARAM}")
130
+ JSON.parse(res.body)
131
+ end
132
+
133
+ # @param [String] key
134
+ # @param [Array<String>] versions
135
+ def assign_fixversions(key, versions)
136
+ key = sanitise_key(key)
137
+
138
+ payload = [{
139
+ "op" => "replace",
140
+ "path" => "/fields/Microsoft.VSTS.Build.IntegrationBuild",
141
+ "value" => versions.join("; ")
142
+ }]
143
+ @network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
144
+ end
145
+
146
+ # @param [String] key
147
+ # @return [Array<String>] fix versions
148
+ def get_ticket_fixversions(key)
149
+ key = sanitise_key(key)
150
+
151
+ get_work_item(key)["fields"]["Microsoft.VSTS.Build.IntegrationBuild"].split("; ")
152
+ end
153
+
154
+ # @param [Apadmi::Grout::Issue] issue
155
+ # @param [String] state_name
156
+ def transition_issue(issue, state_name)
157
+ key = sanitise_key(issue.key)
158
+
159
+ payload = [{
160
+ "op" => "add",
161
+ "path" => "/fields/System.State",
162
+ "value" => state_name
163
+ }]
164
+ @network_service.do_patch("/wit/workitems/#{key}?#{API_VERSION_PARAM}", payload.to_json)
165
+ end
166
+
167
+ # @param [String] key
168
+ # @return [String]
169
+ def get_ticket_status(key)
170
+ key = sanitise_key(key)
171
+
172
+ get_work_item(key)["fields"]["System.State"]
173
+ end
174
+
175
+ private
176
+
177
+ def sanitise_key(key)
178
+ key = key.to_s.strip.delete_prefix "#"
179
+ raise "Invalid key" if key.blank?
180
+
181
+ key
182
+ end
183
+
184
+ # @param wiql_query [String]
185
+ # @return [Array<Apadmi::Grout::Issue>]
186
+ def get_issues_by_wiql(wiql_query)
187
+ @logger.message("Executing wiql")
188
+ @logger.message(wiql_query)
189
+
190
+ payload = { "query" => wiql_query }
191
+ res = @network_service.do_post("/wit/wiql?#{API_VERSION_PARAM}", payload.to_json)
192
+ parsed = JSON.parse(res.body)
193
+ items = parsed["workItems"] || []
194
+ ids = items.map { |item| item["id"] } || []
195
+ find_issues_by_keys(ids)
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apadmi
4
+ module Grout
5
+ # A generic board service to be implemented by whatever
6
+ # board providers we have e.g. JIRA and ADO
7
+ class BoardService
8
+ # @param [String[]] keys
9
+ # @return [Array<Apadmi::Grout::Issue>]
10
+ def find_issues_by_keys(_keys)
11
+ raise "Unimplemented :("
12
+ end
13
+
14
+ # @param [String] _component
15
+ # @param [String] _status
16
+ # @param [String[]] _ticket_types
17
+ # @param [Apadmi::Grout::JiraFindTicketsOptions|Apadmi::Grout::JiraFindTicketsOptions] _options
18
+ # @return [Array<Apadmi::Grout::Issue>]
19
+ def search_unblocked_issues(_component, _status, _ticket_types = [], _options = nil)
20
+ raise "Unimplemented :("
21
+ end
22
+
23
+ # @param [String] _key
24
+ # @param [String] _comment
25
+ def flag_ticket(_key, _comment)
26
+ raise "Unimplemented :("
27
+ end
28
+
29
+ # @param [String] _key
30
+ def un_flag_ticket(_key)
31
+ raise "Unimplemented :("
32
+ end
33
+
34
+ # @param [String] _key
35
+ # @return bool
36
+ def flagged?(_key)
37
+ raise "Unimplemented :("
38
+ end
39
+
40
+ # @param [String] _key
41
+ # @param [Array<String>] _version_strings
42
+ def assign_fixversions(_key, _version_strings)
43
+ raise "Unimplemented :("
44
+ end
45
+
46
+ # @param [String] _key
47
+ # @return [Array<String>]
48
+ def get_ticket_fixversions(_key)
49
+ raise "Unimplemented :("
50
+ end
51
+
52
+ # @param [Apadmi::Grout::Issue] _issue
53
+ # @param [String] _state_name
54
+ def transition_issue(_issue, _state_name)
55
+ raise "Unimplemented :("
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,23 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Build tools
4
3
  require "jira-ruby"
5
- require "faraday"
6
4
 
7
5
  module Apadmi
8
6
  module Grout
9
7
  # Provides a layer of abstraction on top of the Jira api
10
- class JiraWrapper
11
- APPLICATION_JSON = "application/json"
12
- CONTENT_TYPE = "Content-Type"
13
- private_constant :CONTENT_TYPE, :APPLICATION_JSON
14
-
8
+ class JiraBoardService < BoardService
15
9
  # @param [String] username
16
10
  # @param [String] token
17
11
  # @param [String] base_url
18
12
  # @param [String] context_path
19
13
  # @param [String] project
20
- def initialize(username, token, base_url, context_path, project)
14
+ # @param [Apadmi::Grout::NetworkService] network_service
15
+ def initialize(username, token, base_url, context_path, project, network_service)
21
16
  @options = {
22
17
  username: username,
23
18
  password: token,
@@ -26,29 +21,27 @@ module Apadmi
26
21
  auth_type: :basic
27
22
  }
28
23
  @project = project
24
+ @network_service = network_service
29
25
  @jira_client = JIRA::Client.new(@options)
30
26
  end
31
27
 
32
- # @return returns the ticket prefix for the given project
33
- def issue_id_prefix
34
- @project
35
- end
36
-
37
28
  # @param [String[]] keys
38
- # @return [Array<JIRA::Resource::Issue>]
29
+ # @return [Array<Apadmi::Grout::Issue>]
39
30
  def find_issues_by_keys(keys)
40
31
  return [] if keys.length <= 0
41
32
 
42
33
  jql_search = "project = '#{@project}' AND issue IN (#{keys.join(", ")})"
43
- @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
34
+ issues = @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
35
+ convert(issues)
44
36
  end
45
37
 
46
38
  # @param [String] component
47
39
  # @param [String] status
48
40
  # @param [String[]] ticket_types
49
- # @param [Boolean] allow_no_sprint
50
- # @return [Array<JIRA::Resource::Issue>]
51
- def search_unblocked_issues(component, status, ticket_types = [], allow_no_sprint: false)
41
+ # @param [Apadmi::Grout::JiraFindTicketsOptions] options
42
+ # @return [Array<Apadmi::Grout::Issue>]
43
+ def search_unblocked_issues(component, status, ticket_types = [], options = nil)
44
+ allow_no_sprint = options&.include_no_sprint_tickets || false
52
45
  component_filter = (" AND component = '#{component}' " unless component.empty?) || ""
53
46
  status_filter = (" AND status = '#{status}' " unless status.empty?) || ""
54
47
  type_filter = ("AND (#{ticket_types.map { |type| "type = #{type}" }.join("OR ")})" unless ticket_types.empty?) || ""
@@ -61,20 +54,31 @@ module Apadmi
61
54
  AND Flagged is EMPTY
62
55
  }
63
56
 
64
- @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
57
+ issues = @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
58
+ convert(issues)
59
+ end
60
+
61
+ # @param [Apadmi::Grout::Issue] issue
62
+ # @param [String] state_name
63
+ def transition_issue(issue, state_name)
64
+ transitions = @jira_client.Transition.all(issue: issue.raw_object)
65
+ transition = transitions.find { |elem| elem.name.downcase == state_name.downcase }
66
+
67
+ trans = issue.raw_object.transitions.build
68
+ trans.save!("transition" => { "id" => transition.id })
65
69
  end
66
70
 
67
71
  # @param [String] key
68
72
  # @param [String] comment
69
73
  def flag_ticket(key, comment)
70
74
  payload = "{\"flag\":true,\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"#{comment}\"}"
71
- do_post("#{@options[:site]}/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
75
+ @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
72
76
  end
73
77
 
74
78
  # @param [String] key
75
79
  def un_flag_ticket(key)
76
80
  payload = "{\"flag\":false,\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"Unflagged by CI\"}"
77
- do_post("#{@options[:site]}/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
81
+ @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
78
82
  end
79
83
 
80
84
  # @param [String] key
@@ -89,7 +93,7 @@ module Apadmi
89
93
  # @param [String] comment
90
94
  def add_comment(key, comment)
91
95
  payload = "{\"issueKeys\":[\"#{key}\"],\"commentVisibility\":\"\",\"comment\":\"#{comment}\"}"
92
- do_post("#{@options[:site]}/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
96
+ @network_service.do_post("/rest/greenhopper/1.0/xboard/issue/flag/flag.json", payload)
93
97
  end
94
98
 
95
99
  # @param [String] key
@@ -104,14 +108,14 @@ module Apadmi
104
108
  # @param [String] key
105
109
  # @param [String] comment_id
106
110
  def remove_comment(key, comment_id)
107
- do_delete("#{@options[:site]}/rest/api/2/issue/#{key}/comment/#{comment_id}")
111
+ @network_service.do_delete("/rest/api/2/issue/#{key}/comment/#{comment_id}")
108
112
  end
109
113
 
110
114
  # @param [String] component_key
111
- # @return [Array<JIRA::Resource::Issue>]
115
+ # @return [Array<Apadmi::Grout::Issue>]
112
116
  def get_tickets_by_component(component_key)
113
117
  jql_search = "project = '#{@project}' AND component IN ('#{component_key}')"
114
- @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
118
+ convert(@jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq)
115
119
  end
116
120
 
117
121
  # @param [String] key
@@ -120,33 +124,18 @@ module Apadmi
120
124
  @jira_client.Transition.all(issue: issue)
121
125
  end
122
126
 
123
- # @param [String] ticket
124
- # @param [String] state_name
125
- def transition_ticket(ticket, state_name)
126
- issue = @jira_client.Issue.find(ticket)
127
- transition_issue(issue, state_name)
128
- end
129
-
130
- # @param [JIRA::Resource::Issue] issue
131
- # @param [String] state_name
132
- def transition_issue(issue, state_name)
133
- transitions = @jira_client.Transition.all(issue: issue)
134
- transition = transitions.find { |elem| elem.name == state_name }
135
-
136
- trans = issue.transitions.build
137
- trans.save!("transition" => { "id" => transition.id })
138
- end
139
-
140
127
  # @param [String] key
128
+ # @return [String]
141
129
  def get_ticket_status(key)
142
130
  issue = @jira_client.Issue.find(key)
143
- issue.status
131
+ issue.status.name
144
132
  end
145
133
 
146
- # @param [JIRA::Resource::Issue] issue
134
+ # @param [Apadmi::Grout::Issue] issue
147
135
  # @return [Array<PullRequest>]
148
136
  def get_ticket_prs(issue)
149
- response = do_get("#{@options[:site]}/rest/dev-status/latest/issue/details?issueId=#{issue.id}&applicationType=bitbucket&dataType=pullrequest")
137
+ query_string = "issueId=#{issue.raw_object.id}&applicationType=bitbucket&dataType=pullrequest"
138
+ response = @network_service.do_get("/rest/dev-status/latest/issue/details?#{query_string}")
150
139
 
151
140
  return [] if JSON.parse(response.body)["detail"].empty?
152
141
 
@@ -157,11 +146,11 @@ module Apadmi
157
146
 
158
147
  # @param [String] keys
159
148
  # @param [String] component
160
- # @return [Array<JIRA::Resource::Issue>]
149
+ # @return [Array<Apadmi::Grout::Issue>]
161
150
  def get_ticket_subtask(keys, component = nil)
162
151
  jql_search = "project = '#{@project}' AND parent IN #{keys.to_s.gsub("[", "(").gsub("]", ")")}"\
163
152
  + (component.nil? ? "" : " AND component IN ('#{component}')")
164
- @jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq
153
+ convert(@jira_client.Issue.jql(jql_search, { max_results: 1000 }).uniq)
165
154
  end
166
155
 
167
156
  # @param [String] release_date
@@ -182,7 +171,7 @@ module Apadmi
182
171
  def delete_version(version_id, move_version_id)
183
172
  payload = "{\"moveFixIssuesTo\": #{move_version_id}, \"moveAffectedIssuesTo\": #{move_version_id}, "\
184
173
  "\"customFieldReplacementList\": []}"
185
- do_post("#{@options[:site]}/rest/api/2/version/#{version_id}/removeAndSwap", payload)
174
+ @network_service.do_post("/rest/api/2/version/#{version_id}/removeAndSwap", payload)
186
175
  end
187
176
 
188
177
  # @return [Array<JIRA::Resource::Version>]
@@ -190,75 +179,44 @@ module Apadmi
190
179
  @jira_client.Project.find(@project).versions
191
180
  end
192
181
 
193
- # @param [String] ticket
194
- # @param [Array<JIRA::Resource::Version>] versions
195
- def assign_fixversions(ticket, versions)
182
+ # @param [String] key
183
+ # @param [Array<String>] version_strings
184
+ def assign_fixversions(key, version_strings)
185
+ versions = create_or_get_versions(version_strings)
196
186
  fixversions = versions.map { |v| "{\"id\": \"#{v.id}\"}" }.join(", ")
197
187
  payload = "{\"fields\" : {\"fixVersions\": [#{fixversions}] }}"
198
- do_put("#{@options[:site]}/rest/api/2/issue/#{ticket}", payload)
199
- end
200
-
201
- # @param [String] ticket
202
- # @return [Array<Version>]
203
- def get_ticket_fixversions(ticket)
204
- response = do_get("#{@options[:site]}/rest/api/2/issue/#{ticket}")
205
- JSON.parse(response.body)["fields"]["fixVersions"].map do |version|
206
- Version.new(version)
207
- end
208
- end
209
-
210
- # @param [Faraday::Response] response
211
- def throw_if_error(response)
212
- raise "Network call failed #{response.status} #{response.body}" unless (200..210).include?(response.status)
188
+ @network_service.do_put("/rest/api/2/issue/#{key}", payload)
213
189
  end
214
190
 
215
- # @param [String] uri
216
- # @return [Faraday::Response]
217
- def do_get(uri)
218
- conn = setup_con
219
- response = conn.get(uri, CONTENT_TYPE => APPLICATION_JSON)
220
- throw_if_error(response)
221
- response
222
- end
223
-
224
- # @param [String] uri
225
- # @param [String] payload
226
- # @return [Faraday::Response]
227
- def do_put(uri, payload)
228
- conn = setup_con
229
- response = conn.put(uri, payload, CONTENT_TYPE => APPLICATION_JSON)
230
- throw_if_error(response)
231
- response
191
+ # @param [String] key
192
+ # @return [Array<String>]
193
+ def get_ticket_fixversions(key)
194
+ response = @network_service.do_get("/rest/api/2/issue/#{key}")
195
+ JSON.parse(response.body)["fields"]["fixVersions"].map { |v| v["name"] } || []
232
196
  end
233
197
 
234
- # @param [String] uri
235
- # @param [String] payload
236
- # @return [Faraday::Response]
237
- def do_post(uri, payload)
238
- conn = setup_con
239
- response = conn.post(uri, payload, CONTENT_TYPE => APPLICATION_JSON)
240
- throw_if_error(response)
241
- response
242
- end
198
+ private
243
199
 
244
- # @param [String] uri
245
- # @return [Faraday::Response]
246
- def do_delete(uri)
247
- conn = setup_con
248
- response = conn.delete(uri, CONTENT_TYPE => APPLICATION_JSON)
249
- throw_if_error(response)
250
- response
200
+ # @param [Array<String>] version_strings
201
+ # @return [Array<JIRA::Resource::Version>]
202
+ def create_or_get_versions(version_strings)
203
+ all = all_versions
204
+ version_strings.map do |version|
205
+ existing_version = all.find { |v| v.name == version }
206
+ if !existing_version.nil?
207
+ existing_version
208
+ else
209
+ date = Time.now.strftime("%Y-%m-%d")
210
+ create_version(date, version)
211
+ end
212
+ end
251
213
  end
252
214
 
253
- # @return [Faraday::Connection]
254
- def setup_con
255
- auth_str = "#{@options[:username]}:#{@options[:password]}"
256
- conn = Faraday.new # create a new Connection with base URL
257
- conn.headers["Authorization"] = "Basic #{Base64.strict_encode64(auth_str)}"
258
- conn
215
+ # @param [Array<JIRA::Resource::Issue>]
216
+ # @return [Array<Apadmi::Grout::Issue>]
217
+ def convert(issues)
218
+ Apadmi::Grout::Issue.from_jira_issues(issues, @options[:site])
259
219
  end
260
-
261
- private :setup_con, :do_delete, :do_get, :do_post, :do_put
262
220
  end
263
221
  end
264
222
  end
@@ -25,6 +25,14 @@ module Apadmi
25
25
  stdout.strip
26
26
  end
27
27
 
28
+ # Runs a git fetch all
29
+ def self.fetch_all
30
+ stdout, stderr, = Open3.capture3("git fetch --all")
31
+ raise "Failed to fetch #{stderr}" unless stderr.strip.empty?
32
+
33
+ stdout.strip
34
+ end
35
+
28
36
  # Gets all the merges accessible from the current HEAD which matches at least one of the given grep conditions
29
37
  # @param grep_conditions [Array<String>] values to be passed in as grep cases (https://git-scm.com/docs/git-log)
30
38
  def self.merge_changelog(grep_conditions)
@@ -34,6 +42,16 @@ module Apadmi
34
42
 
35
43
  stdout
36
44
  end
45
+
46
+ # Gets all the merges that are NOT accessible from HEAD which matches at least one of the given grep conditions
47
+ # @param grep_conditions [Array<String>] values to be passed in as grep cases (https://git-scm.com/docs/git-log)
48
+ def self.invert_changelog(grep_conditions)
49
+ command = "git log --all ^HEAD --merges --format=%s#{grep_conditions.map { |c| " --grep #{c}" }.join(" ")}"
50
+ stdout, stderr, = Open3.capture3(command)
51
+ raise "Failed to get changelog: #{stderr}" unless stderr.strip.empty?
52
+
53
+ stdout
54
+ end
37
55
  end
38
56
  end
39
57
  end