jirametrics 2.30.1pre1 → 2.30.1pre2

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: c1b70c2648c844b678a4b20ae4f9ed570e2a471ff306b4b682d1d0566ba9c334
4
- data.tar.gz: ce6972d092d1f5b1bd7425f9d19a8dbbb1d2a7f584e0fe48b9f140faaca96e57
3
+ metadata.gz: 06c3a3f5e8296c0acf9dfeb03bf42e7773ae59676c45af1fa9c5c9e40ee00fa1
4
+ data.tar.gz: 30fc6cfdd1857336ad9ac6a73166803c319197ca2b29ddbb055453742d02532b
5
5
  SHA512:
6
- metadata.gz: 98e35757d64618ff0ee87854a88b98196ccf87cb47761c4a065ead4aae84cb6653cbb37c6746f06a61418fee4c75f31089172028b485b276939f3c07d99b045d
7
- data.tar.gz: 1c509febc055eec7ca7d8d9a89a9136e6b6ded8ff8b0585c9b1432b0e2aeb8a8d7301808070eb52b8919e6340928b9aecf137e98a2acd79e6c271fd36a1bc83a
6
+ metadata.gz: 17b036d6a1f1b8ef6282e001e8befe9c64744cc3b8f529175068762ca1771a1ede57ab3b3dc86744c086e29480d00a1c75636e6784df656f23f1be826b03fc54
7
+ data.tar.gz: 8f5d9af1c3ab342d93a1d43a0d6c8df3342889412e3e7a7398febf9d1775b831885e85f3b7b4d5841a5e7bbb4f3f2717c245672246cefc1506771b1e016fc0d4
@@ -30,7 +30,7 @@ class DownloadConfig
30
30
  end
31
31
 
32
32
  def github_repo *repos
33
- github_repos.concat(repos.map { |r| normalize_github_repo(r) })
33
+ github_repos.concat(repos)
34
34
  end
35
35
 
36
36
  def start_date today:
@@ -42,8 +42,4 @@ class DownloadConfig
42
42
 
43
43
  private
44
44
 
45
- def normalize_github_repo repo
46
- match = repo.match(%r{github\.com/([^/]+/[^/]+?)/?$})
47
- match ? match[1] : repo
48
- end
49
45
  end
@@ -101,6 +101,7 @@ class DownloaderForCloud < Downloader
101
101
  )
102
102
 
103
103
  attach_changelog_to_issues issue_datas: issue_datas, issue_jsons: response['issues']
104
+ attach_worklogs_to_issues issue_datas: issue_datas, issue_jsons: response['issues']
104
105
 
105
106
  response['issues'].each do |issue_json|
106
107
  issue_json['exporter'] = {
@@ -129,6 +130,44 @@ class DownloaderForCloud < Downloader
129
130
  issue_datas
130
131
  end
131
132
 
133
+ def attach_worklogs_to_issues issue_datas:, issue_jsons:
134
+ max_results = 100
135
+
136
+ issue_jsons.each do |issue_json|
137
+ worklog = issue_json['fields']['worklog']
138
+ next unless worklog
139
+
140
+ total = worklog['total'].to_i
141
+ all_worklogs = worklog['worklogs'] || []
142
+ next if all_worklogs.size >= total
143
+
144
+ key = issue_json['key']
145
+ start_at = all_worklogs.size
146
+
147
+ loop do
148
+ response = @jira_gateway.call_url(
149
+ relative_url: "/rest/api/3/issue/#{CGI.escape(key)}/worklog?startAt=#{start_at}&maxResults=#{max_results}"
150
+ )
151
+
152
+ worklogs = response['worklogs'] || []
153
+ all_worklogs.concat(worklogs)
154
+
155
+ break if all_worklogs.size >= response['total'].to_i
156
+
157
+ start_at += worklogs.size
158
+ end
159
+
160
+ issue_json['fields']['worklog'] = {
161
+ 'startAt' => 0,
162
+ 'maxResults' => all_worklogs.size,
163
+ 'total' => all_worklogs.size,
164
+ 'worklogs' => all_worklogs
165
+ }
166
+
167
+ log " Enhanced #{key} with #{all_worklogs.size} worklogs"
168
+ end
169
+ end
170
+
132
171
  def attach_changelog_to_issues issue_datas:, issue_jsons:
133
172
  max_results = 10_000 # The max jira accepts is 10K
134
173
  payload = {
@@ -190,7 +229,7 @@ class DownloaderForCloud < Downloader
190
229
  unless stale.empty?
191
230
  log_start ' Downloading more issues ' unless in_related_phase
192
231
  stale.each_slice(100) do |slice|
193
- slice = bulk_fetch_issues(issue_datas: slice, board: board, in_initial_query: true)
232
+ slice = bulk_fetch_issues(issue_datas: slice, board: board, in_initial_query: !in_related_phase)
194
233
  progress_dot
195
234
  slice.each do |data|
196
235
  next unless data.issue
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'worklog_helper'
4
+
3
5
  class DownloaderForDataCenter < Downloader
6
+ include WorklogHelper
7
+
4
8
  def jira_instance_type
5
9
  'Jira DataCenter'
6
10
  end
@@ -49,8 +53,12 @@ class DownloaderForDataCenter < Downloader
49
53
  }
50
54
  identify_other_issues_to_be_downloaded raw_issue: issue_json, board: board
51
55
  file = "#{issue_json['key']}-#{board.id}.json"
56
+ issue_path = File.join(path, file)
57
+
58
+ @file_system.save_json(json: issue_json, filename: issue_path)
52
59
 
53
- @file_system.save_json(json: issue_json, filename: File.join(path, file))
60
+ # Fetch complete worklog data for this issue
61
+ enhance_issue_with_worklogs(issue_key: issue_json['key'], issue_path: issue_path)
54
62
  end
55
63
 
56
64
  total = json['total'].to_i
@@ -99,17 +99,36 @@ class GithubGateway
99
99
  Regexp.new("\\b(?:#{keys_pattern})-\\d+(?![A-Za-z0-9])")
100
100
  end
101
101
 
102
- def run_command args
103
- stdout, stderr, status = Open3.capture3('gh', *args)
102
+ TRANSIENT_HTTP_ERRORS = [429, 500, 502, 503, 504].freeze
103
+ MAX_RETRIES = 3
104
104
 
105
- # This extra check seems to only matter on Windows. On the mac, auth failures don't pass status.success?
106
- if stderr.include?('SAML enforcement')
107
- raise "GitHub CLI is not authorized to access #{@repo}. " \
108
- 'Run: gh auth refresh -h github.com -s read:org'
109
- end
105
+ def run_command args
106
+ attempts = 0
107
+ loop do
108
+ attempts += 1
109
+ stdout, stderr, status = Open3.capture3('gh', *args)
110
+
111
+ # This extra check seems to only matter on Windows. On the mac, auth failures don't pass status.success?
112
+ if stderr.include?('SAML enforcement')
113
+ raise "GitHub CLI is not authorized to access #{@repo}. " \
114
+ 'Run: gh auth refresh -h github.com -s read:org'
115
+ end
110
116
 
111
- raise "GitHub CLI command failed for #{@repo}: #{stderr}" unless status.success?
117
+ unless status.success?
118
+ if attempts < MAX_RETRIES && TRANSIENT_HTTP_ERRORS.any? { |code| stderr.include?("HTTP #{code}") }
119
+ delay = 2**attempts
120
+ @file_system.log " GitHub returned transient error for #{@repo}: #{stderr.strip}. Retrying in #{delay}s (attempt #{attempts}/#{MAX_RETRIES})...", also_write_to_stderr: true
121
+ sleep delay
122
+ next
123
+ end
124
+ raise "GitHub CLI command failed for #{@repo}: #{stderr}"
125
+ end
112
126
 
113
- JSON.parse(stdout)
127
+ result = JSON.parse(stdout)
128
+ if result.nil? || (result.is_a?(Array) && result.empty?)
129
+ @file_system.warning "No data was found in GitHub for #{@repo}. Is that what you expected?"
130
+ end
131
+ return result
132
+ end
114
133
  end
115
134
  end
@@ -40,7 +40,7 @@ class JiraGateway
40
40
  return parse_response(command: command, result: stdout)
41
41
  end
42
42
 
43
- if RETRYABLE_EXIT_CODES.include?(status.exitstatus) && retries < MAX_RETRIES
43
+ if RETRYABLE_EXIT_CODES.include?(status.exitstatus) && retries < MAX_RETRIES && !stderr.include?('503')
44
44
  retries += 1
45
45
  @file_system.log "Transient network error (exit #{status.exitstatus}), retrying in #{RETRY_DELAY_SECONDS}s (attempt #{retries}/#{MAX_RETRIES})..."
46
46
  sleep_between_retries
@@ -53,6 +53,11 @@ class JiraGateway
53
53
  if stderr.include?('401')
54
54
  raise 'The request was not authorized. Verify that your authentication token hasn\'t expired'
55
55
  end
56
+ if stderr.include?('503')
57
+ raise 'Jira returned 503 (Service Unavailable). This may be a temporary outage, or your ' \
58
+ 'Jira account may have been deactivated due to inactivity. Check your Jira subscription ' \
59
+ 'and try again later.'
60
+ end
56
61
  raise "Failed call with exit status #{status.exitstatus}. " \
57
62
  "See #{@file_system.logfile_name} for details"
58
63
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorklogHelper
4
+ def enhance_issue_with_worklogs issue_key:, issue_path:
5
+ all_worklogs = []
6
+ start_at = 0
7
+ max_results = 100
8
+
9
+ loop do
10
+ url = "/rest/api/2/issue/#{CGI.escape(issue_key)}/worklog?startAt=#{start_at}&maxResults=#{max_results}"
11
+ response = @jira_gateway.call_url(relative_url: url)
12
+
13
+ worklogs = response['worklogs'] || []
14
+ all_worklogs.concat(worklogs)
15
+
16
+ total = response['total'].to_i
17
+ break if start_at + worklogs.size >= total
18
+
19
+ start_at += max_results
20
+ end
21
+
22
+ # Reload the saved issue and update worklogs
23
+ issue_json = @file_system.load_json(issue_path)
24
+ issue_json['worklog'] = {
25
+ 'startAt' => 0,
26
+ 'maxResults' => all_worklogs.size,
27
+ 'total' => all_worklogs.size,
28
+ 'worklogs' => all_worklogs
29
+ }
30
+ @file_system.save_json(json: issue_json, filename: issue_path)
31
+
32
+ log " Enhanced #{issue_key} with #{all_worklogs.size} worklogs" if all_worklogs.any?
33
+ end
34
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirametrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.30.1pre1
4
+ version: 2.30.1pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler
@@ -188,6 +188,7 @@ files:
188
188
  - lib/jirametrics/user.rb
189
189
  - lib/jirametrics/value_equality.rb
190
190
  - lib/jirametrics/wip_by_column_chart.rb
191
+ - lib/jirametrics/worklog_helper.rb
191
192
  homepage: https://jirametrics.org
192
193
  licenses:
193
194
  - Apache-2.0
@@ -210,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
211
  - !ruby/object:Gem::Version
211
212
  version: '0'
212
213
  requirements: []
213
- rubygems_version: 4.0.10
214
+ rubygems_version: 4.0.13
214
215
  specification_version: 4
215
216
  summary: Extract Jira metrics
216
217
  test_files: []