fastlane-plugin-jira_issues_release_notes 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +254 -16
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_comment_action.rb +200 -0
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/{branch_jira_issues_release_notes_action.rb → jira_feature_validation_action.rb} +16 -47
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_issues_keys_from_commits_action.rb +134 -0
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_release_changelog_action.rb +182 -0
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_release_validation_action.rb +173 -0
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_versions_action.rb +162 -0
- data/lib/fastlane/plugin/jira_issues_release_notes/helper/jira_issues_release_notes_helper.rb +212 -17
- data/lib/fastlane/plugin/jira_issues_release_notes/version.rb +1 -1
- metadata +11 -7
- data/lib/fastlane/plugin/jira_issues_release_notes/actions/jira_issues_release_notes_action.rb +0 -289
@@ -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
|
data/lib/fastlane/plugin/jira_issues_release_notes/helper/jira_issues_release_notes_helper.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
+
" - <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
|
-
|
17
|
-
|
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.
|
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
|
-
|
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(
|
65
|
-
return [] if
|
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 (#{
|
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
|
|