apadmi_grout 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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