apadmi_grout 3.0.0.pre → 3.0.0.pre3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cc48519c5e224b346eaa77b07d4953135348913e6a084d5fef4c4182f4592a4
4
- data.tar.gz: 48ba7a5d12682e02463243336f9bcea8ffb35d728f2cc8f1512551e736a96444
3
+ metadata.gz: b3d9943e3bd7d774d59d0feaef83862207ae074275e24bd228c0cac4874dc0ab
4
+ data.tar.gz: f79e4681f4a5289e9004f0c1dbaeaf3f1342c765858255c19ea2a733cee4b7fb
5
5
  SHA512:
6
- metadata.gz: 57cadb72f942f55fc386ac2c8669038bbc83a7418f5c8537332555f85a9950e1fd116eacef5ccaece3bed42e0cc1cf76bfa9cf59b262c3325ae911f61eac92e0
7
- data.tar.gz: 0a18b6dce1c6dde627f3931bc320967ebfb89908ecd0f6faa95ad280697a7201258922e65c6b26e64ce1f3c86ddceb25a9446b0773b5d73bb2f132537504ab7a
6
+ metadata.gz: eccdfe011afc6c1af759199b25beaf375436aef06574d47c347cf8699839830c815b28eb9dbf444d793110e3f4a2adfcf3adb178b2c300446b2f42fbec934e38
7
+ data.tar.gz: 2f893c7b6d7e383cfc199f59752a1574e6660a2a5fb191028816a1dfa0712e5cd19c2db48cf113f348e1142f39c6e9d2ebfc0a30567484d7fa3aa00186a3bd48
data/CHANGELOG.md CHANGED
@@ -7,6 +7,7 @@
7
7
  * Support analysing squash merges for release notes (breaking change: renamed `merge_changelog` function in git_utils)
8
8
  * Add optional environment field to release notes, displayed between version and build date
9
9
  * Replace Bitrise env vars, and add a migration doc to v3
10
+ * Support linking pages by URL as related work to Jira fix versions
10
11
 
11
12
  ## [2.10.0] - 2025-09-11
12
13
  * Temporarily point jira-ruby active pull request to point to JIRA apis that haven't been removed.
@@ -60,7 +60,7 @@ module Apadmi
60
60
  final_list = issues.filter do |issue|
61
61
  # Flag the ticket if it doesn't include in either changelog since
62
62
  # this means the ticket is likely in an incorrect state
63
- decide_should_include(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
63
+ decide_should_include?(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
64
64
  end
65
65
 
66
66
  @logger.message("Final list: #{final_list.map(&:key).join(", ")}")
@@ -69,7 +69,7 @@ module Apadmi
69
69
 
70
70
  private
71
71
 
72
- def decide_should_include(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
72
+ def decide_should_include?(changelog_ids, custom_flag_messages, invert_changelog_ids, issue)
73
73
  if !changelog_ids.include?(issue.key) && !invert_changelog_ids.include?(issue.key)
74
74
  @ado_board_service.flag_ticket(
75
75
  issue.key,
@@ -68,7 +68,7 @@ module Apadmi
68
68
  issues.filter do |issue|
69
69
  # Decide whether to include this ticket based on PRs
70
70
  status = process_prs(issue, custom_flag_messages)
71
- decide_should_include(issue, status, changelog_ids)
71
+ decide_should_include?(issue, status, changelog_ids)
72
72
  end
73
73
  else
74
74
  issues
@@ -78,7 +78,7 @@ module Apadmi
78
78
  # @param issue [Apadmi::Grout::Issue]
79
79
  # @param pr_status [Int] status of whether or not we can move
80
80
  # @param changelog_ids [Array<String>] the ticket ids pulled from git
81
- def decide_should_include(issue, pr_status, changelog_ids)
81
+ def decide_should_include?(issue, pr_status, changelog_ids)
82
82
  # For merged PRs, check if the ticket appears in the changelog.
83
83
  # If it doesn't then it's likely it was merged AFTER this release build was triggered and shouldn't be moved
84
84
  # by this build
@@ -27,7 +27,7 @@ module Apadmi
27
27
  private
28
28
 
29
29
  def render_classified_issues(template, classified_issues)
30
- return if classified_issues.empty
30
+ return if classified_issues.empty?
31
31
 
32
32
  CustomMustache.render(
33
33
  template,
@@ -10,8 +10,7 @@ module Apadmi
10
10
  :move_tickets_action,
11
11
  :find_tickets_to_move_action,
12
12
  :issues_from_changelog_action,
13
- :generate_release_notes_action,
14
- keyword_init: true
13
+ :generate_release_notes_action
15
14
  ) do
16
15
  # @param [Apadmi::Grout::JiraConfig] jira_config
17
16
  # @param [Logger] logger
@@ -6,8 +6,7 @@ module Apadmi
6
6
  :personal_access_token,
7
7
  :base_url,
8
8
  :flag_tag,
9
- :expand_type,
10
- keyword_init: true
9
+ :expand_type
11
10
  ) do
12
11
  def initialize(personal_access_token:,
13
12
  base_url:,
@@ -9,7 +9,7 @@ module Apadmi
9
9
  STATUS_ABORTED_SUCCESS = 4
10
10
 
11
11
  BitriseBuild = Struct.new(:triggered_at, :slug, :status, :commit_hash) do
12
- def finished_with_success
12
+ def finished_with_success?
13
13
  status == STATUS_SUCCESS
14
14
  end
15
15
  end
@@ -17,7 +17,7 @@ module Apadmi
17
17
  Artifact = Struct.new(:title, :download_url)
18
18
 
19
19
  TriggeredBitriseBuild = Struct.new(:build_number, :build_slug, :build_url, :message, :service, :slug, :status, :triggered_workflow) do
20
- def finished_with_success
20
+ def finished_with_success?
21
21
  status == STATUS_SUCCESS
22
22
  end
23
23
 
@@ -5,9 +5,7 @@ module Apadmi
5
5
  ConfluenceConfig = Struct.new(
6
6
  :base_url,
7
7
  :client_id,
8
- :client_secret,
9
- keyword_init: true
10
- ) do
11
- end
8
+ :client_secret
9
+ )
12
10
  end
13
11
  end
@@ -3,7 +3,7 @@
3
3
  module Apadmi
4
4
  module Grout
5
5
  # Generic find tickets options
6
- class FindTicketsOptions; end
6
+ class FindTicketsOptions; end # rubocop:disable Lint/EmptyClass
7
7
 
8
8
  # Jira specific find tickets options
9
9
  class JiraFindTicketsOptions < FindTicketsOptions
@@ -7,8 +7,7 @@ module Apadmi
7
7
  :api_token,
8
8
  :base_url,
9
9
  :context_path,
10
- :project_key,
11
- keyword_init: true
10
+ :project_key
12
11
  )
13
12
  end
14
13
  end
@@ -15,7 +15,7 @@ module Apadmi
15
15
  :others
16
16
  ) do
17
17
  # @return returns true if all categories are empty
18
- def empty
18
+ def empty?
19
19
  tasks.empty? && features.empty? &&
20
20
  improvements.empty? && defects.empty? && others.empty?
21
21
  end
@@ -45,19 +45,19 @@ module Apadmi
45
45
  :templates,
46
46
  :environment
47
47
  ) do
48
- def has_min_os_version
48
+ def has_min_os_version?
49
49
  !min_os_version.nil? && !min_os_version.blank?
50
50
  end
51
51
 
52
- def has_environment
52
+ def has_environment?
53
53
  !environment.nil? && !environment.blank?
54
54
  end
55
55
 
56
- def has_moved_issues
56
+ def has_moved_issues?
57
57
  !moved_issues.empty?
58
58
  end
59
59
 
60
- def has_release_issues
60
+ def has_release_issues?
61
61
  !release_issues.empty?
62
62
  end
63
63
  end
@@ -10,31 +10,31 @@ module Apadmi
10
10
  ### Version
11
11
  {{config.app_version}}
12
12
 
13
- {{#config.has_environment}}
13
+ {{#config.has_environment?}}
14
14
  ### Environment
15
15
  {{config.environment}}
16
16
 
17
- {{/config.has_environment}}
17
+ {{/config.has_environment?}}
18
18
  ### Build date
19
19
  {{config.date}}
20
20
 
21
- {{#config.has_min_os_version}}
21
+ {{#config.has_min_os_version?}}
22
22
  ### Minimum Supported OS
23
23
  {{config.min_os_version}}
24
24
 
25
- {{/config.has_min_os_version}}
25
+ {{/config.has_min_os_version?}}
26
26
  ### Build Details
27
27
  Commit Hash: {{config.commit_hash}}
28
28
  [CI/CD build \#{{config.ci_build_number}}]({{config.ci_build_url}})
29
29
 
30
- {{#config.has_moved_issues}}
30
+ {{#config.has_moved_issues?}}
31
31
  ## Tickets Moved By This Build
32
32
  {{ rendered_moved_issues }}
33
- {{/config.has_moved_issues}}
34
- {{#config.has_release_issues}}
33
+ {{/config.has_moved_issues?}}
34
+ {{#config.has_release_issues?}}
35
35
  ## Sprint Release notes
36
36
  {{ rendered_release_issues }}
37
- {{/config.has_release_issues}}}
37
+ {{/config.has_release_issues?}}}
38
38
  end
39
39
 
40
40
  def default_list_template
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open-uri"
3
+ require "net/http"
4
4
  require "fileutils"
5
5
 
6
6
  module Apadmi
@@ -59,11 +59,7 @@ module Apadmi
59
59
  FileUtils.mkdir_p output_dir
60
60
 
61
61
  artifacts.each do |item|
62
- File.open("#{output_dir}/#{item.title}", "wb") do |file|
63
- # rubocop:disable Security/Open
64
- file.write URI.open(item.download_url).read
65
- # rubocop:enable Security/Open
66
- end
62
+ File.binwrite("#{output_dir}/#{item.title}", Net::HTTP.get(URI(item.download_url)))
67
63
  end
68
64
  artifacts
69
65
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./board_service"
3
+ require_relative "board_service"
4
4
 
5
5
  module Apadmi
6
6
  module Grout
@@ -169,14 +169,14 @@ module Apadmi
169
169
 
170
170
  payload = if user
171
171
  [{
172
- "op": "add",
173
- "path": "/fields/System.AssignedTo",
174
- "value": user
172
+ op: "add",
173
+ path: "/fields/System.AssignedTo",
174
+ value: user
175
175
  }]
176
176
  else
177
177
  [{
178
- "op": "remove",
179
- "path": "/fields/System.AssignedTo"
178
+ op: "remove",
179
+ path: "/fields/System.AssignedTo"
180
180
  }]
181
181
  end
182
182
  update_ticket(key, payload)
@@ -218,11 +218,11 @@ module Apadmi
218
218
 
219
219
  # @return [String]
220
220
  def get_options_filter(component, options = nil)
221
- tags = (options&.required_tags || []).push(*component).filter { |it| !it.blank? }
222
- not_tags = (options&.not_tags || []).filter { |it| !it.blank? }
221
+ tags = (options&.required_tags || []).push(*component).filter { |tag| !tag.blank? }
222
+ not_tags = (options&.not_tags || []).filter { |tag| !tag.blank? }
223
223
  additional_fields = (options&.additional_fields || {}).reject { |k, _v| k.blank? }
224
224
 
225
- raise "Tags can either be required or not required, not both" unless (tags & not_tags).empty?
225
+ raise "Tags can either be required or not required, not both" if tags.intersect?(not_tags)
226
226
 
227
227
  tags_filter = tags.map { |tag| " AND [System.Tags] CONTAINS '#{tag}' " }.join(" ")
228
228
  not_tags_filter = not_tags.map { |tag| " AND [System.Tags] NOT CONTAINS '#{tag}'" }.join(" ")
@@ -37,12 +37,31 @@ module Apadmi
37
37
  raise "Unimplemented :("
38
38
  end
39
39
 
40
+ # @param [String] _version_name
41
+ # @param [Boolean, nil] _released
42
+ # @param [String, nil] _description
43
+ def update_version(_version_name, _released: nil, _description: nil)
44
+ raise "Unimplemented :("
45
+ end
46
+
47
+ # @param [Array<Apadmi::Grout::Issue>] _issues
48
+ # @param [Array<String>] _version_names
49
+ def assign_fixversion_to_tickets(_issues, _version_names)
50
+ raise "Unimplemented :("
51
+ end
52
+
40
53
  # @param [String] _key
41
54
  # @param [Array<String>] _version_strings
42
55
  def assign_fixversions(_key, _version_strings)
43
56
  raise "Unimplemented :("
44
57
  end
45
58
 
59
+ # @param [String] _key
60
+ # @param [Array<String>] _version_names
61
+ def remove_fixversions(_key, _version_names)
62
+ raise "Unimplemented :("
63
+ end
64
+
46
65
  # @param [String] _key
47
66
  # @return [Array<String>]
48
67
  def get_ticket_fixversions(_key)
@@ -54,6 +73,13 @@ module Apadmi
54
73
  def transition_issue(_issue, _state_name)
55
74
  raise "Unimplemented :("
56
75
  end
76
+
77
+ # @param [String] _version_name
78
+ # @param [String] _url
79
+ # @param [String] _title
80
+ def add_version_related_work(_version_name, _url, _title)
81
+ raise "Unimplemented :("
82
+ end
57
83
  end
58
84
  end
59
85
  end
@@ -100,7 +100,7 @@ module Apadmi
100
100
  jql_search = "project = '#{@project}' AND issue IN (#{key})"
101
101
  response = @jira_client.Issue.jql(jql_search, { fields: ["comment"], max_results: 1000 }).uniq
102
102
  comments = response[0].comments.reverse
103
- !comments.empty? ? comments[0] : nil
103
+ comments.empty? ? nil : comments[0]
104
104
  end
105
105
 
106
106
  # @param [String] key
@@ -149,7 +149,7 @@ module Apadmi
149
149
  # @param [String] component
150
150
  # @return [Array<Apadmi::Grout::Issue>]
151
151
  def get_ticket_subtask(keys, component = nil)
152
- jql_search = "project = '#{@project}' AND parent IN #{keys.to_s.gsub("[", "(").gsub("]", ")")}"\
152
+ jql_search = "project = '#{@project}' AND parent IN #{keys.to_s.gsub("[", "(").gsub("]", ")")}" \
153
153
  + (component.nil? ? "" : " AND component IN ('#{component}')")
154
154
  convert(@jira_client.Issue.jql(jql_search, { max_results: 1000, fields: ["*navigable"] }).uniq)
155
155
  end
@@ -160,9 +160,9 @@ module Apadmi
160
160
  def create_version(release_date, name)
161
161
  version = @jira_client.Version.build
162
162
  version.save!({
163
- "project": @project,
164
- "name": name,
165
- "releaseDate": release_date
163
+ project: @project,
164
+ name: name,
165
+ startDate: release_date
166
166
  })
167
167
  version
168
168
  end
@@ -170,7 +170,7 @@ module Apadmi
170
170
  # @param [int] version_id
171
171
  # @param [int] move_version_id
172
172
  def delete_version(version_id, move_version_id)
173
- payload = "{\"moveFixIssuesTo\": #{move_version_id}, \"moveAffectedIssuesTo\": #{move_version_id}, "\
173
+ payload = "{\"moveFixIssuesTo\": #{move_version_id}, \"moveAffectedIssuesTo\": #{move_version_id}, " \
174
174
  "\"customFieldReplacementList\": []}"
175
175
  @network_service.do_post("/rest/api/2/version/#{version_id}/removeAndSwap", payload)
176
176
  end
@@ -180,6 +180,41 @@ module Apadmi
180
180
  @jira_client.Project.find(@project).versions
181
181
  end
182
182
 
183
+ # @param [String] version_name
184
+ # @param [Boolean, nil] released omit to leave unchanged
185
+ # @param [String, nil] description omit to leave unchanged
186
+ def update_version(version_name, released: nil, description: nil)
187
+ raise ArgumentError, "Must provide at least one of: released, description" if released.nil? && description.nil?
188
+
189
+ version = all_versions.find { |v| v.name == version_name }
190
+ raise "Version '#{version_name}' not found" if version.nil?
191
+
192
+ fields = {}
193
+ fields[:released] = released unless released.nil?
194
+ fields[:description] = description unless description.nil?
195
+ @network_service.do_put("/rest/api/2/version/#{version.id}", fields.to_json)
196
+ end
197
+
198
+ # Assigns one or more fix versions to all given issues that are missing them.
199
+ # Filters in-memory from the already-fetched issue objects — no extra network calls.
200
+ # Each issue gets a single PUT containing only the versions it is missing.
201
+ # @param [Array<Apadmi::Grout::Issue>] issues
202
+ # @param [Array<String>] version_names
203
+ def assign_fixversion_to_tickets(issues, version_names)
204
+ return if issues.empty? || version_names.empty?
205
+
206
+ versions = create_or_get_versions(version_names)
207
+
208
+ issues.each do |issue|
209
+ existing_ids = issue.raw_object.fixVersions.map(&:id)
210
+ missing = versions.reject { |v| existing_ids.include?(v.id) }
211
+ next if missing.empty?
212
+
213
+ adds = missing.map { |v| "{\"add\": {\"id\": \"#{v.id}\"}}" }.join(", ")
214
+ @network_service.do_put("/rest/api/2/issue/#{issue.key}", "{\"update\": {\"fixVersions\": [#{adds}]}}")
215
+ end
216
+ end
217
+
183
218
  # @param [String] key
184
219
  # @param [Array<String>] version_strings
185
220
  def assign_fixversions(key, version_strings)
@@ -189,6 +224,16 @@ module Apadmi
189
224
  @network_service.do_put("/rest/api/2/issue/#{key}", payload)
190
225
  end
191
226
 
227
+ # Removes specific fix versions from a ticket using the update.remove operation.
228
+ # @param [String] key
229
+ # @param [Array<String>] version_names
230
+ def remove_fixversions(key, version_names)
231
+ return if version_names.empty?
232
+
233
+ removes = version_names.map { |name| "{\"remove\": {\"name\": \"#{name}\"}}" }.join(", ")
234
+ @network_service.do_put("/rest/api/2/issue/#{key}", "{\"update\": {\"fixVersions\": [#{removes}]}}")
235
+ end
236
+
192
237
  # @param [String] key
193
238
  # @return [Array<String>]
194
239
  def get_ticket_fixversions(key)
@@ -196,6 +241,22 @@ module Apadmi
196
241
  JSON.parse(response.body)["fields"]["fixVersions"].map { |v| v["name"] } || []
197
242
  end
198
243
 
244
+ # Adds a related work link to a Jira version, skipping if a link with the same URL already exists.
245
+ # @param [String] version_name
246
+ # @param [String] url
247
+ # @param [String] title
248
+ def add_version_related_work(version_name, url, title)
249
+ version = all_versions.find { |v| v.name == version_name }
250
+ raise "Version '#{version_name}' not found" if version.nil?
251
+
252
+ response = @network_service.do_get("/rest/api/3/version/#{version.id}/relatedwork")
253
+ existing_links = JSON.parse(response.body) || []
254
+ return if existing_links.any? { |link| link["url"] == url }
255
+
256
+ payload = { category: "link", url: url, title: title }.to_json
257
+ @network_service.do_post("/rest/api/3/version/#{version.id}/relatedwork", payload)
258
+ end
259
+
199
260
  private
200
261
 
201
262
  # @param [Array<String>] version_strings
@@ -204,11 +265,11 @@ module Apadmi
204
265
  all = all_versions
205
266
  version_strings.map do |version|
206
267
  existing_version = all.find { |v| v.name == version }
207
- if !existing_version.nil?
208
- existing_version
209
- else
268
+ if existing_version.nil?
210
269
  date = Time.now.strftime("%Y-%m-%d")
211
270
  create_version(date, version)
271
+ else
272
+ existing_version
212
273
  end
213
274
  end
214
275
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module AndroidBuildType
4
- AAB = "aab"
5
- APK = "apk"
6
- end
7
-
8
3
  module Apadmi
9
4
  module Grout
5
+ module AndroidBuildType
6
+ AAB = "aab"
7
+ APK = "apk"
8
+ end
9
+
10
10
  # Generic Command parsing Utils
11
11
  class CommandParsingUtils
12
12
  def self.determine_build_type(command)
@@ -10,9 +10,9 @@ module Apadmi
10
10
  @repo_path_arg = repo_path.nil? ? "" : " -C #{repo_path} "
11
11
  end
12
12
 
13
- # rubocop:disable Style/ClassVars:
13
+ # rubocop:disable Style/ClassVars
14
14
  @@default = Apadmi::Grout::GitUtils.new(nil)
15
- # rubocop:enable Style/ClassVars:
15
+ # rubocop:enable Style/ClassVars
16
16
 
17
17
  # Gets the root of the Git repo we're in
18
18
  # @return [String] The full path for the root of this Git repo
@@ -103,7 +103,7 @@ module Apadmi
103
103
 
104
104
  # Helper function to check if a repo is a shallow clone
105
105
  # @return [Boolean] If the repo is a shallow clone
106
- def is_shallow_clone
106
+ def is_shallow_clone?
107
107
  stdout, stderr, status = Open3.capture3("git #{@repo_path_arg} rev-parse --is-shallow-repository")
108
108
  raise "Failed to check if repo is shallow clone: #{stderr}" unless status.success?
109
109
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Apadmi
4
4
  module Grout
5
- VERSION = "3.0.0.pre"
5
+ VERSION = "3.0.0.pre3"
6
6
  end
7
7
  end
data/lib/apadmi_grout.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  require "active_support/core_ext/object/blank"
4
4
 
5
- Dir[File.expand_path("apadmi/grout/**/*.rb", File.dirname(__FILE__))].sort.each do |current|
5
+ Dir[File.expand_path("apadmi/grout/**/*.rb", File.dirname(__FILE__))].each do |current|
6
6
  require current
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apadmi_grout
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.pre
4
+ version: 3.0.0.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Apadmi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-26 00:00:00.000000000 Z
11
+ date: 2026-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday