fastlane-plugin-jira_issues_release_notes 0.4.0 → 1.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.
@@ -0,0 +1,162 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/jira_issues_release_notes_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ FL_JIRA_LAST_TAG ||= :S3_APK_OUTPUT_PATH
8
+ FL_JIRA_LAST_TAG_HASH ||= :FL_JIRA_LAST_TAG_HASH
9
+ FL_JIRA_COMMITS_FROM_HASH ||= :FL_JIRA_COMMITS_FROM_HASH
10
+ FL_JIRA_LAST_KEYS_FROM_COMMITS ||= :FL_JIRA_LAST_KEYS_FROM_COMMITS
11
+ FL_JIRA_LAST_ISSUES_FROM_COMMITS ||= :FL_JIRA_LAST_ISSUES_FROM_COMMITS
12
+ end
13
+
14
+ class JiraVersionsAction < Action
15
+ def self.run(params)
16
+ @jira_helper = Helper::JiraIssuesReleaseNotesHelper.initialize_jira(
17
+ host: params[:host],
18
+ api_version: params[:api_version],
19
+ username: params[:username],
20
+ password: params[:password],
21
+ context_path: params[:context_path],
22
+ disable_ssl_verification: params[:disable_ssl_verification]
23
+ )
24
+
25
+ versions = @jira_helper.list_versions(
26
+ project_id_or_key: params[:project_id],
27
+ query: params[:query],
28
+ order_by: params[:order_by],
29
+ status: params[:status]
30
+ )
31
+
32
+ versions.map { |version|
33
+ {
34
+ :id => version.self,
35
+ :name => version.name,
36
+ :archived => version.archived,
37
+ :released => version.released,
38
+ :user_release_date => defined? version.userReleaseDate ? version.userReleaseDate : nil,
39
+ :overdue => defined? version.overdue ? version.overdue : false,
40
+ :projectId => version.projectId,
41
+ }
42
+ }
43
+ end
44
+
45
+ def self.description
46
+ "It generates a release note based on the issues keys found in branch name and descriptions found in the commits"
47
+ end
48
+
49
+ def self.authors
50
+ ["Erick Martins"]
51
+ end
52
+
53
+ def self.return_value
54
+ "boolean value"
55
+ end
56
+
57
+ def self.details
58
+ # Optional:
59
+ "It generates a release note based on the issues keys found in branch name and descriptions found in the commits"
60
+ end
61
+
62
+ def self.available_options
63
+ conflict_extraction_method = Proc.new do |other|
64
+ UI.user_error! "Unexpected conflict with option #{other}" unless [:extract_from_branch, :tag_prefix].include?(other)
65
+ end
66
+
67
+ conflict_comment = Proc.new do |other|
68
+ UI.user_error! "Unexpected conflict with option #{other}" unless [:comment_block, :comment].include?(other)
69
+ end
70
+
71
+ [
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :project_id,
74
+ env_name: 'FL_JIRA_PROJECT_ID',
75
+ description: 'The project ID or project key',
76
+ optional: false,
77
+ ),
78
+ FastlaneCore::ConfigItem.new(
79
+ key: :query,
80
+ description: 'Filter the results using a literal string. Versions with matching name or description are returned (case insensitive).',
81
+ optional: true,
82
+ ),
83
+ FastlaneCore::ConfigItem.new(
84
+ key: :order_by,
85
+ description: 'Order the results by a field. Valid values: description, -description, +description, name, -name, +name, releaseDate, -releaseDate, +releaseDate, sequence, -sequence, +sequence, startDate, -startDate, +startDate',
86
+ optional: true,
87
+ verify_block: proc do |value|
88
+ valid_order = ["description", "-description", "+description", "name", "-name", "+name", "releaseDate", "-releaseDate", "+releaseDate", "sequence", "-sequence", "+sequence", "startDate", "-startDate", "+startDate"]
89
+ UI.user_error!("Invalid :order_by value! Valid values: #{valid_order.join(", ")}") unless valid_order.include?(value)
90
+ end
91
+ ),
92
+ FastlaneCore::ConfigItem.new(
93
+ key: :status,
94
+ description: 'A list of status values used to filter the results by version status. This parameter accepts a comma-separated list. The status values are released, unreleased, and archived',
95
+ optional: true,
96
+ verify_block: proc do |value|
97
+ valid_values = ["released", "unreleased", "archived"]
98
+ UI.user_error!("Invalid :status value! Valid values: #{valid_values.join(", ")}") unless valid_values.include?(value)
99
+ end
100
+ ),
101
+
102
+ # Jira Client options
103
+ FastlaneCore::ConfigItem.new(
104
+ key: :username,
105
+ env_name: 'FL_JIRA_USERNAME',
106
+ description: 'Jira user',
107
+ optional: false
108
+ ),
109
+ FastlaneCore::ConfigItem.new(
110
+ key: :password,
111
+ env_name: 'FL_JIRA_PASSWORD',
112
+ description: 'Jira user',
113
+ optional: false
114
+ ),
115
+ FastlaneCore::ConfigItem.new(
116
+ key: :host,
117
+ env_name: 'FL_JIRA_HOST',
118
+ description: 'Jira location',
119
+ optional: false
120
+ ),
121
+ FastlaneCore::ConfigItem.new(
122
+ key: :api_version,
123
+ env_name: 'FL_JIRA_API_VERSION',
124
+ description: 'Jira api version',
125
+ default_value: '3',
126
+ optional: true,
127
+ ),
128
+ FastlaneCore::ConfigItem.new(
129
+ key: :context_path,
130
+ env_name: 'FL_JIRA_CONTEXT_PATH',
131
+ description: 'Jira context path',
132
+ optional: true,
133
+ default_value: ''
134
+ ),
135
+ FastlaneCore::ConfigItem.new(
136
+ key: :disable_ssl_verification,
137
+ env_name: 'FL_JIRA_DISABLE_SSL_VERIFICATION',
138
+ description: 'Jira SSL Verification mode',
139
+ optional: true,
140
+ default_value: false,
141
+ type: Boolean
142
+ ),
143
+ FastlaneCore::ConfigItem.new(
144
+ key: :debug,
145
+ description: "True if you want to log out a debug info",
146
+ default_value: false,
147
+ type: Boolean,
148
+ optional: true
149
+ )
150
+ ]
151
+ end
152
+
153
+ def self.is_supported?(platform)
154
+ # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
155
+ # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
156
+ #
157
+ # [:ios, :mac, :android].include?(platform)
158
+ true
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,29 +1,213 @@
1
1
  require 'fastlane_core/ui/ui'
2
- require 'jira-ruby'
2
+ require 'zk-jira-ruby'
3
3
 
4
4
  module Fastlane
5
5
  UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
6
6
 
7
7
  module Helper
8
8
  class JiraIssuesReleaseNotesHelper
9
- # class methods that you define here become available in your action
10
- # as `Helper::JiraIssuesReleaseNotesHelper.your_method`
11
- #
12
- def self.show_message
13
- UI.message("Hello from the jira_issues_release_notes plugin helper!")
9
+
10
+ def self.format_issue_link(issue:)
11
+ # formats the link according to the output format we need
12
+ link = @jira_helper.url(issue: issue)
13
+ case @format
14
+ when "slack"
15
+ "*<#{link}|#{issue.key}>*: #{issue.summary}"
16
+ when "markdown"
17
+ "- **[#{issue.key}](#{link})**: #{issue.summary}"
18
+ when "html"
19
+ "&nbsp;&nbsp;- <strong><a href=\"#{link}\" target=\"_blank\">#{issue.key}</a><strong>: #{issue.summary}"
20
+ else
21
+ "- #{issue.key}: #{issue.summary} (#{link})"
22
+ end
23
+ end
24
+
25
+ def self.style_text(text:, style:)
26
+ # formats the text according to the style we're looking to use
27
+
28
+ # Skips all styling
29
+ case style
30
+ when "title"
31
+ case @format
32
+ when "markdown"
33
+ "# #{text}"
34
+ when "slack"
35
+ "*#{text}*"
36
+ when "html"
37
+ "<h1>#{text}</h1>"
38
+ else
39
+ text
40
+ end
41
+ when "heading"
42
+ case @format
43
+ when "markdown"
44
+ "### #{text}"
45
+ when "slack"
46
+ "*#{text}*"
47
+ when "html"
48
+ "<h3>#{text}</h3>"
49
+ else
50
+ "#{text}:"
51
+ end
52
+ when "bold"
53
+ case @format
54
+ when "markdown"
55
+ "**#{text}**"
56
+ when "slack"
57
+ "*#{text}*"
58
+ when "html"
59
+ "<strong>#{text}</strong>"
60
+ else
61
+ text
62
+ end
63
+ else
64
+ text # catchall, shouldn't be needed
65
+ end
66
+ end
67
+
68
+ def self.get_commit_after_latest_tag(tag_regex:, tag_version_match:, debug:)
69
+ unless Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_COMMITS_FROM_HASH] then
70
+
71
+ # Try to find the tag
72
+ command = "git describe --tags --match=#{tag_regex}"
73
+ tag = Actions.sh(command, log: debug)
74
+
75
+ if tag.empty?
76
+ UI.message("First commit of the branch is taken as a begining of next release")
77
+ # If there is no tag found we taking the first commit of current branch
78
+ hash = Actions.sh('git rev-list --max-parents=0 HEAD', log: debug).chomp
79
+ else
80
+ # Tag's format is v2.3.4-5-g7685948
81
+ # Get a hash of last version tag
82
+ tag_name = tag.split('-')[0...-2].join('-').strip
83
+ parsed_version = tag_name.match(tag_version_match)
84
+
85
+ if parsed_version.nil?
86
+ UI.user_error!("Error while parsing version from tag #{tag_name} by using tag_version_match - #{tag_version_match}. Please check if the tag contains version as you expect and if you are using single brackets for tag_version_match parameter.")
87
+ end
88
+
89
+ version = parsed_version[0]
90
+
91
+ # Get a hash of last version tag
92
+ command = "git rev-list -n 1 refs/tags/#{tag_name}"
93
+ hash = Actions.sh(command, log: debug).chomp
94
+
95
+ UI.message("Found a tag #{tag_name} associated with version #{version}")
96
+ end
97
+
98
+ # Get commits log between last version and head
99
+ commits = git_log(
100
+ pretty: '%s|%b|>',
101
+ start: hash,
102
+ debug: debug
103
+ )
104
+
105
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_TAG] = tag
106
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_TAG_HASH] = hash
107
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_COMMITS_FROM_HASH] = commits
108
+ end
109
+
110
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_COMMITS_FROM_HASH]
111
+ rescue StandardError => error
112
+ UI.error error.message
113
+ UI.message("Tag was not found for match pattern - #{tag_regex}")
114
+ nil
14
115
  end
15
116
 
16
- # class methods that you define here become available in your action
17
- # as `Helper::SemanticConventionReleaseHelper.your_method`
18
- #
117
+ def self.get_key_from_commit_after_latest_tag(tag_regex:, tag_version_match:, issue_key_regex:, debug:)
118
+ unless Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_KEYS_FROM_COMMITS] then
119
+ commits = get_commit_after_latest_tag(
120
+ tag_regex: tag_regex,
121
+ tag_version_match: tag_version_match,
122
+ debug: debug,
123
+ )
124
+
125
+ return nil unless commits
126
+
127
+ last_keys_from_commits = commits
128
+ .to_enum(:scan, issue_key_regex)
129
+ .map { $& }
130
+ .reject(&:empty?)
131
+ .uniq
132
+
133
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_KEYS_FROM_COMMITS] = last_keys_from_commits
134
+ end
135
+
136
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_KEYS_FROM_COMMITS]
137
+ end
138
+
139
+ def self.get_issues_from_commit_after_latest_tag(tag_regex:, tag_version_match:, issue_key_regex:, debug:)
140
+ unless Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_ISSUES_FROM_COMMITS] then
141
+ unless @jira_helper then
142
+ UI.user_error! "Uninitialized jira client"
143
+ end
144
+
145
+ keys = get_key_from_commit_after_latest_tag(
146
+ tag_regex: tag_regex,
147
+ tag_version_match: tag_version_match,
148
+ issue_key_regex: issue_key_regex,
149
+ debug: debug
150
+ )
151
+
152
+ issues = @jira_helper.get(keys: keys)
153
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_ISSUES_FROM_COMMITS] = issues
154
+ end
155
+
156
+ Actions.lane_context[Fastlane::Actions::SharedValues::FL_JIRA_LAST_ISSUES_FROM_COMMITS]
157
+ end
158
+
19
159
  def self.git_log(params)
20
160
  command = "git log --pretty='#{params[:pretty]}' --reverse #{params[:start]}..HEAD"
21
161
  Actions.sh(command, log: params[:debug]).chomp
22
162
  end
23
163
 
24
- def self.jira_helper(host:, username:, password:, context_path:, disable_ssl_verification:)
164
+ def self.generate_changelog(groups:, line_break_format:)
165
+ changelog = []
166
+
167
+ groups.each do |label, issues|
168
+ changelog.concat(['']) unless changelog.to_s.empty?
169
+ changelog.concat(format_issues(label: label, issues: issues)) unless issues.empty?
170
+ end
171
+ # changes = format_issues(issues: issues)
172
+
173
+ changelog.join(line_break_format)
174
+ end
175
+
176
+ def self.format_issues(label:, issues:)
177
+ [
178
+ style_text(text: "► #{label}", style: 'heading'),
179
+ issues.map { |issue| format_issue_link(issue: issue) }
180
+ ].flatten!
181
+ end
182
+
183
+ def self.extract_key_from_branch(branch:, ticket_prefix:)
184
+ regex = Regexp.new("(#{ticket_prefix}-\\d+)")
185
+
186
+ ticket_code = regex.match branch
187
+
188
+ ticket_code
189
+ end
190
+
191
+
192
+ def self.initialize_jira(host:, api_version:, username:, password:, context_path:, disable_ssl_verification:)
193
+ unless @jira_helper then
194
+ @jira_helper = jira_helper(
195
+ host: host,
196
+ api_version: api_version,
197
+ username: username,
198
+ password: password,
199
+ context_path: context_path,
200
+ disable_ssl_verification: disable_ssl_verification
201
+ )
202
+ end
203
+
204
+ @jira_helper
205
+ end
206
+
207
+ def self.jira_helper(host:, api_version:, username:, password:, context_path:, disable_ssl_verification:)
25
208
  JiraHelper.new(
26
209
  host: host,
210
+ api_version: api_version,
27
211
  username: username,
28
212
  password: password,
29
213
  context_path: context_path,
@@ -32,13 +216,14 @@ module Fastlane
32
216
  end
33
217
 
34
218
  class JiraClientHelper
35
- def self.client(host:, username:, password:, context_path:, disable_ssl_verification:)
219
+ def self.client(host:, api_version:, username:, password:, context_path:, disable_ssl_verification:)
36
220
  options = {
37
221
  site: host,
38
222
  context_path: context_path,
39
223
  auth_type: :basic,
40
224
  username: username,
41
225
  password: password,
226
+ rest_base_path: "/rest/api/#{api_version}",
42
227
  ssl_verify_mode: disable_ssl_verification ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
43
228
  }
44
229
 
@@ -47,28 +232,28 @@ module Fastlane
47
232
  end
48
233
 
49
234
  class JiraHelper
50
- def initialize(host:, username:, password:, context_path:, disable_ssl_verification:, jira_client_helper: nil)
235
+ def initialize(host:, api_version:, username:, password:, context_path:, disable_ssl_verification:, jira_client_helper: nil)
51
236
  @host = host
52
237
  @context_path = context_path
53
238
 
54
- jira_client_helper ||= JiraClientHelper.client(
239
+ @client ||= JiraClientHelper.client(
55
240
  host: host,
241
+ api_version: api_version,
56
242
  username: username,
57
243
  password: password,
58
244
  context_path: context_path,
59
245
  disable_ssl_verification: disable_ssl_verification
60
246
  )
61
- @client = jira_client_helper
62
247
  end
63
248
 
64
- def get(issues:, extra_fields: [])
65
- return [] if issues.to_a.empty?
249
+ def get(keys:, extra_fields: [])
250
+ return [] if keys.to_a.empty?
66
251
 
67
252
  fields = [:key, :summary, :status, :issuetype, :description]
68
253
  fields.concat extra_fields
69
254
 
70
255
  begin
71
- @client.Issue.jql("KEY IN (#{issues.join(',')})", fields: fields, validate_query: false)
256
+ @client.Issue.jql("KEY IN (#{keys.join(',')})", fields: fields, validate_query: false)
72
257
  rescue StandardError => e
73
258
  UI.important('Jira Client: Failed to get issue.')
74
259
  UI.important("Jira Client: Reason - #{e.message}")
@@ -76,6 +261,16 @@ module Fastlane
76
261
  end
77
262
  end
78
263
 
264
+ def list_versions(project_id_or_key:, query: nil, order_by: nil, status: nil)
265
+ @client.Version.find(
266
+ project_id_or_key: project_id_or_key,
267
+ query: query,
268
+ orderBy: order_by,
269
+ status: status
270
+ )
271
+ end
272
+
273
+
79
274
  def add_comment(comment:, issues:)
80
275
  return if issues.to_a.empty?
81
276