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.
- 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
|
|