jirametrics 2.13 → 2.20.1

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/anonymizer.rb +8 -6
  3. data/lib/jirametrics/atlassian_document_format.rb +8 -4
  4. data/lib/jirametrics/board_config.rb +3 -1
  5. data/lib/jirametrics/change_item.rb +2 -2
  6. data/lib/jirametrics/chart_base.rb +5 -2
  7. data/lib/jirametrics/cycletime_config.rb +22 -3
  8. data/lib/jirametrics/cycletime_histogram.rb +3 -1
  9. data/lib/jirametrics/daily_view.rb +49 -42
  10. data/lib/jirametrics/data_quality_report.rb +6 -3
  11. data/lib/jirametrics/dependency_chart.rb +4 -1
  12. data/lib/jirametrics/downloader.rb +34 -99
  13. data/lib/jirametrics/downloader_for_cloud.rb +202 -0
  14. data/lib/jirametrics/downloader_for_data_center.rb +94 -0
  15. data/lib/jirametrics/examples/standard_project.rb +9 -9
  16. data/lib/jirametrics/expedited_chart.rb +1 -1
  17. data/lib/jirametrics/exporter.rb +10 -5
  18. data/lib/jirametrics/file_system.rb +24 -1
  19. data/lib/jirametrics/flow_efficiency_scatterplot.rb +1 -1
  20. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +1 -1
  21. data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
  22. data/lib/jirametrics/html/cycletime_histogram.erb +2 -2
  23. data/lib/jirametrics/html/index.css +5 -10
  24. data/lib/jirametrics/html/index.erb +2 -34
  25. data/lib/jirametrics/html/index.js +90 -0
  26. data/lib/jirametrics/html/sprint_burndown.erb +5 -3
  27. data/lib/jirametrics/html_report_config.rb +5 -3
  28. data/lib/jirametrics/issue.rb +31 -19
  29. data/lib/jirametrics/jira_gateway.rb +55 -17
  30. data/lib/jirametrics/project_config.rb +30 -3
  31. data/lib/jirametrics/settings.json +3 -1
  32. data/lib/jirametrics.rb +19 -70
  33. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f219c386b574f433dbaf649b9b7cccc42114401b45ff703a7c8f228c52d815c4
4
- data.tar.gz: 68af46ec7256e3b4004c4fd8016d52b17b0fa2bb717e9265b164dc5fa59fca56
3
+ metadata.gz: cbe1101b082615d38939850c0adc688aedefe02a74537503bcd390cdf11d0d4e
4
+ data.tar.gz: eeffbda7c7ba8280273e0d749ede1ed3c1caa33d06a1f50a2c47b2331035d5af
5
5
  SHA512:
6
- metadata.gz: 71d289169562b3e4029549ab0db5ef1c4d25bc933d860f6f2a834f6caa39bbb5de93db2932996b7a5d75b4b6fb16f021ac5666cad3d57092dea6b4db4f8b7e97
7
- data.tar.gz: 425c213d81982d99f9d933d7ad8dbeb4f87f92ec97188d763b51edcb2f7b85a1072da80c0a5df8566a6325223e9b35d98b180e18fd67d1e50cb2d44b1b0e3872
6
+ metadata.gz: b73533c90e457c2c5f7a7f2d1759ccd44d218ebe984052fcb581d8ab88225abf43b5612923217b4fa7467ed11e82ba5b4fe2cc656a324f269b71c2497bf67659
7
+ data.tar.gz: 57cbc54fe6c739d0c85f68cc0efcfbc6005975af0b40174ed9ee35790a83dac9b5e524601b770dffc6a3fefbd10f0d577d19b840560b3acfb63b0b542728d5fc
@@ -2,11 +2,12 @@
2
2
 
3
3
  require 'random-word'
4
4
 
5
- class Anonymizer
5
+ class Anonymizer < ChartBase
6
6
  # needed for testing
7
7
  attr_reader :project_config, :issues
8
8
 
9
9
  def initialize project_config:, date_adjustment: -200
10
+ super()
10
11
  @project_config = project_config
11
12
  @issues = @project_config.issues
12
13
  @all_boards = @project_config.all_boards
@@ -130,18 +131,19 @@ class Anonymizer
130
131
  end
131
132
  end
132
133
 
133
- def shift_all_dates
134
- @file_system.log "Shifting all dates by #{@date_adjustment} days"
134
+ def shift_all_dates date_adjustment: @date_adjustment
135
+ adjustment_in_seconds = 60 * 60 * 24 * date_adjustment
136
+ @file_system.log "Shifting all dates by #{label_days date_adjustment}"
135
137
  @issues.each do |issue|
136
138
  issue.changes.each do |change|
137
- change.time = change.time + @date_adjustment
139
+ change.time = change.time + adjustment_in_seconds
138
140
  end
139
141
 
140
- issue.raw['fields']['updated'] = (issue.updated + @date_adjustment).to_s
142
+ issue.raw['fields']['updated'] = (issue.updated + adjustment_in_seconds).to_s
141
143
  end
142
144
 
143
145
  range = @project_config.time_range
144
- @project_config.time_range = (range.begin + @date_adjustment)..(range.end + @date_adjustment)
146
+ @project_config.time_range = (range.begin + date_adjustment)..(range.end + date_adjustment)
145
147
  end
146
148
 
147
149
  def random_name
@@ -5,6 +5,7 @@ class AtlassianDocumentFormat
5
5
 
6
6
  def initialize users:, timezone_offset:
7
7
  @users = users
8
+ @timezone_offset = timezone_offset
8
9
  end
9
10
 
10
11
  def to_html input
@@ -12,16 +13,19 @@ class AtlassianDocumentFormat
12
13
  input
13
14
  .gsub(/{color:(#\w{6})}([^{]+){color}/, '<span style="color: \1">\2</span>') # Colours
14
15
  .gsub(/\[~accountid:([^\]]+)\]/) { expand_account_id $1 } # Tagged people
15
- .gsub(/\[([^\|]+)\|(https?[^\]]+)\]/, '<a href="\2">\1</a>') # URLs
16
+ .gsub(/\[([^|]+)\|(https?[^\]]+)\]/, '<a href="\2">\1</a>') # URLs
16
17
  .gsub("\n", '<br />')
17
- else
18
+ elsif input&.[]('content')
18
19
  input['content'].collect { |element| adf_node_to_html element }.join("\n")
20
+ else
21
+ # We have an actual ADF document with no content.
22
+ ''
19
23
  end
20
24
  end
21
25
 
22
26
  # ADF is Atlassian Document Format
23
27
  # https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
24
- def adf_node_to_html node
28
+ def adf_node_to_html node # rubocop:disable Metrics/CyclomaticComplexity
25
29
  closing_tag = nil
26
30
  node_attrs = node['attrs']
27
31
 
@@ -153,4 +157,4 @@ class AtlassianDocumentFormat
153
157
  text = "@#{user.display_name}" if user
154
158
  "<span class='account_id'>#{text}</span>"
155
159
  end
156
- end
160
+ end
@@ -11,6 +11,7 @@ class BoardConfig
11
11
 
12
12
  def run
13
13
  @board = @project_config.all_boards[id]
14
+ raise "Can't find board #{id.inspect} in #{@project_config.all_boards.keys.inspect}" unless @board
14
15
 
15
16
  instance_eval(&@block)
16
17
  raise "Must specify a cycletime for board #{@id}" if @board.cycletime.nil?
@@ -23,7 +24,8 @@ class BoardConfig
23
24
  end
24
25
 
25
26
  @board.cycletime = CycleTimeConfig.new(
26
- parent_config: self, label: label, block: block, file_system: project_config.file_system
27
+ parent_config: self, label: label, block: block, file_system: project_config.file_system,
28
+ settings: project_config.settings
27
29
  )
28
30
  end
29
31
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ChangeItem
4
- attr_reader :field, :value_id, :old_value_id, :raw, :time, :author_raw
5
- attr_accessor :value, :old_value
4
+ attr_reader :field, :value_id, :old_value_id, :raw, :author_raw
5
+ attr_accessor :value, :old_value, :time
6
6
 
7
7
  def initialize raw:, author_raw:, time:, artificial: false
8
8
  @raw = raw
@@ -2,7 +2,8 @@
2
2
 
3
3
  class ChartBase
4
4
  attr_accessor :timezone_offset, :board_id, :all_boards, :date_range,
5
- :time_range, :data_quality, :holiday_dates, :settings, :issues, :file_system, :users
5
+ :time_range, :data_quality, :holiday_dates, :settings, :issues, :file_system,
6
+ :atlassian_document_format
6
7
  attr_writer :aggregated_project
7
8
  attr_reader :canvas_width, :canvas_height
8
9
 
@@ -44,7 +45,7 @@ class ChartBase
44
45
 
45
46
  def render_top_text caller_binding
46
47
  result = +''
47
- result << "<h1>#{@header_text}</h1>" if @header_text
48
+ result << "<h1 class='foldable'>#{@header_text}</h1>" if @header_text
48
49
  result << ERB.new(@description_text).result(caller_binding) if @description_text
49
50
  result
50
51
  end
@@ -66,6 +67,8 @@ class ChartBase
66
67
  end
67
68
 
68
69
  def label_days days
70
+ return 'unknown' if days.nil?
71
+
69
72
  "#{days} day#{'s' unless days == 1}"
70
73
  end
71
74
 
@@ -6,12 +6,15 @@ require 'date'
6
6
  class CycleTimeConfig
7
7
  include SelfOrIssueDispatcher
8
8
 
9
- attr_reader :label, :parent_config
9
+ attr_reader :label, :parent_config, :settings, :file_system
10
+
11
+ def initialize parent_config:, label:, block:, settings:, file_system: nil, today: Date.today
10
12
 
11
- def initialize parent_config:, label:, block:, file_system: nil, today: Date.today
12
13
  @parent_config = parent_config
13
14
  @label = label
14
15
  @today = today
16
+ @settings = settings
17
+ @cache_cycletime_calculations = settings['cache_cycletime_calculations']
15
18
 
16
19
  # If we hit something deprecated and this is nil then we'll blow up. Although it's ugly, this
17
20
  # may make it easier to find problems in the test code ;-)
@@ -63,6 +66,10 @@ class CycleTimeConfig
63
66
  end
64
67
 
65
68
  def started_stopped_changes issue
69
+ cache_key = "#{issue.key}:#{issue.board.id}"
70
+ last_result = (@cache ||= {})[cache_key]
71
+ return *last_result if last_result && @cache_cycletime_calculations
72
+
66
73
  started = @start_at.call(issue)
67
74
  stopped = @stop_at.call(issue)
68
75
 
@@ -80,7 +87,15 @@ class CycleTimeConfig
80
87
  # for the start and not have it conflict.
81
88
  started = nil if started&.time == stopped&.time
82
89
 
83
- [started, stopped]
90
+ result = [started, stopped]
91
+ if last_result && result != last_result
92
+ @file_system.error(
93
+ "Calculation mismatch; this could break caching. #{issue.inspect} new=#{result.inspect}, " \
94
+ "previous=#{last_result.inspect}"
95
+ )
96
+ end
97
+ @cache[cache_key] = result
98
+ result
84
99
  end
85
100
 
86
101
  def started_stopped_times issue
@@ -88,6 +103,10 @@ class CycleTimeConfig
88
103
  [started&.time, stopped&.time]
89
104
  end
90
105
 
106
+ def flush_cache
107
+ @cache = nil
108
+ end
109
+
91
110
  def started_stopped_dates issue
92
111
  started_time, stopped_time = started_stopped_times(issue)
93
112
  [started_time&.to_date, stopped_time&.to_date]
@@ -62,7 +62,9 @@ class CycletimeHistogram < ChartBase
62
62
  )
63
63
  end
64
64
 
65
- return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if data_sets.empty?
65
+ if data_sets.empty?
66
+ return "<h1 class='foldable'>#{@header_text}</h1><div>No data matched the selected criteria. Nothing to show.</div>"
67
+ end
66
68
 
67
69
  wrap_and_render(binding, __FILE__)
68
70
  end
@@ -23,7 +23,7 @@ class DailyView < ChartBase
23
23
  def run
24
24
  aging_issues = select_aging_issues
25
25
 
26
- return "<h1>#{@header_text}</h1>There are no items currently in progress" if aging_issues.empty?
26
+ return "<h1 class='foldable'>#{@header_text}</h1>There are no items currently in progress" if aging_issues.empty?
27
27
 
28
28
  result = +''
29
29
  result << render_top_text(binding)
@@ -33,10 +33,6 @@ class DailyView < ChartBase
33
33
  result
34
34
  end
35
35
 
36
- def atlassian_document_format
37
- @atlassian_document_format ||= AtlassianDocumentFormat.new(users: users, timezone_offset: timezone_offset)
38
- end
39
-
40
36
  def select_aging_issues
41
37
  aging_issues = issues.select do |issue|
42
38
  started_at, stopped_at = issue.board.cycletime.started_stopped_times(issue)
@@ -82,7 +78,7 @@ class DailyView < ChartBase
82
78
  blocked_stalled = issue.blocked_stalled_by_date(
83
79
  date_range: today..today, chart_end_time: time_range.end, settings: settings
84
80
  )[today]
85
- return [] unless blocked_stalled
81
+ return [] if blocked_stalled.active?
86
82
 
87
83
  lines = []
88
84
  if blocked_stalled.blocked?
@@ -102,15 +98,18 @@ class DailyView < ChartBase
102
98
  lines
103
99
  end
104
100
 
105
- def make_issue_label issue
106
- "<img src='#{issue.type_icon_url}' title='#{issue.type}' class='icon' /> " \
107
- "<b><a href='#{issue.url}'>#{issue.key}</a></b> &nbsp;<i>#{issue.summary}</i>"
101
+ def make_issue_label issue:, done:
102
+ label = "<img src='#{issue.type_icon_url}' title='#{issue.type}' class='icon' /> "
103
+ label << '<s>' if done
104
+ label << "<b><a href='#{issue.url}'>#{issue.key}</a></b> &nbsp;<i>#{issue.summary}</i>"
105
+ label << '</s>' if done
106
+ label
108
107
  end
109
108
 
110
- def make_title_line issue
109
+ def make_title_line issue:, done:
111
110
  title_line = +''
112
111
  title_line << color_block('--expedited-color', title: 'Expedited') if issue.expedited?
113
- title_line << make_issue_label(issue)
112
+ title_line << make_issue_label(issue: issue, done: done)
114
113
  title_line
115
114
  end
116
115
 
@@ -119,20 +118,25 @@ class DailyView < ChartBase
119
118
  parent_key = issue.parent_key
120
119
  if parent_key
121
120
  parent = issues.find_by_key key: parent_key, include_hidden: true
122
- text = parent ? make_issue_label(parent) : parent_key
121
+ text = parent ? make_issue_label(issue: parent, done: parent.done?) : parent_key
123
122
  lines << ["Parent: #{text}"]
124
123
  end
125
124
  lines
126
125
  end
127
126
 
128
- def make_stats_lines issue
127
+ def make_stats_lines issue:, done:
129
128
  line = []
130
129
 
131
130
  line << "<img src='#{issue.priority_url}' class='icon' /> <b>#{issue.priority_name}</b>"
132
131
 
133
- age = issue.board.cycletime.age(issue, today: date_range.end)
134
- line << "Age: <b>#{age ? label_days(age) : '(Not Started)'}</b>"
132
+ if done
133
+ cycletime = issue.board.cycletime.cycletime(issue)
135
134
 
135
+ line << "Cycletime: <b>#{label_days cycletime}</b>"
136
+ else
137
+ age = issue.board.cycletime.age(issue, today: date_range.end)
138
+ line << "Age: <b>#{age ? label_days(age) : '(Not Started)'}</b>"
139
+ end
136
140
  line << "Status: <b>#{format_status issue.status, board: issue.board}</b>"
137
141
 
138
142
  column = issue.board.visible_columns.find { |c| c.status_ids.include?(issue.status.id) }
@@ -158,13 +162,14 @@ class DailyView < ChartBase
158
162
 
159
163
  def make_child_lines issue
160
164
  lines = []
161
- subtasks = issue.subtasks.reject { |i| i.done? }
165
+ subtasks = issue.subtasks
166
+
167
+ return lines if subtasks.empty?
168
+
169
+ lines << '<section><div class="foldable">Child issues</div>'
170
+ lines += subtasks
171
+ lines << '</section>'
162
172
 
163
- unless subtasks.empty?
164
- icon_urls = subtasks.collect(&:type_icon_url).uniq.collect { |url| "<img src='#{url}' class='icon' />" }
165
- lines << (icon_urls << 'Incomplete child issues')
166
- lines += subtasks
167
- end
168
173
  lines
169
174
  end
170
175
 
@@ -172,16 +177,11 @@ class DailyView < ChartBase
172
177
  history = issue.changes.reverse
173
178
  lines = []
174
179
 
175
- id = next_id
176
- lines << [
177
- "<a href=\"javascript:toggle_visibility('open#{id}', 'close#{id}', 'table#{id}');\">" \
178
- "<span id='open#{id}'>▶ Issue History</span>" \
179
- "<span id='close#{id}' style='display: none'>▼ Issue History</span></a>"
180
- ]
180
+ lines << '<section><div class="foldable startFolded">Issue history</div>'
181
181
  table = +''
182
- table << "<table id='table#{id}' style='display: none'>"
182
+ table << '<table>'
183
183
  history.each do |c|
184
- time = c.time.strftime '%b %d, %I:%M%P'
184
+ time = c.time.strftime '%b %d, %Y @ %I:%M%P'
185
185
 
186
186
  table << '<tr>'
187
187
  table << "<td><span class='time' title='Timestamp: #{c.time}'>#{time}</span></td>"
@@ -192,23 +192,24 @@ class DailyView < ChartBase
192
192
  end
193
193
  table << '</table>'
194
194
  lines << [table]
195
+ lines << '</section>'
195
196
  lines
196
197
  end
197
198
 
198
199
  def history_text change:, board:
200
+ convertor = ->(value, _id) { value.inspect }
201
+ convertor = ->(_value, id) { format_status(board.possible_statuses.find_by_id(id), board: board) } if change.status?
202
+
199
203
  if change.comment? || change.description?
200
204
  atlassian_document_format.to_html(change.value)
201
- elsif change.status?
202
- convertor = ->(id) { format_status(board.possible_statuses.find_by_id(id), board: board) }
203
- to = convertor.call(change.value_id)
205
+ elsif %w[status priority assignee duedate issuetype].include?(change.field)
206
+ to = convertor.call(change.value, change.value_id)
204
207
  if change.old_value
205
- from = convertor.call(change.old_value_id)
208
+ from = convertor.call(change.old_value, change.old_value_id)
206
209
  "Changed from #{from} to #{to}"
207
210
  else
208
211
  "Set to #{to}"
209
212
  end
210
- elsif %w[priority assignee duedate issuetype].include?(change.field)
211
- "Changed from \"#{change.old_value}\" to \"#{change.value}\""
212
213
  elsif change.flagged?
213
214
  change.value == '' ? 'Off' : 'On'
214
215
  else
@@ -242,15 +243,19 @@ class DailyView < ChartBase
242
243
  end
243
244
 
244
245
  def assemble_issue_lines issue, child:
246
+ done = issue.done?
247
+
245
248
  lines = []
246
- lines << [make_title_line(issue)]
249
+ lines << [make_title_line(issue: issue, done: done)]
247
250
  lines += make_parent_lines(issue) unless child
248
- lines += make_stats_lines(issue)
249
- lines += make_description_lines(issue)
250
- lines += make_sprints_lines(issue)
251
- lines += make_blocked_stalled_lines(issue)
252
- lines += make_child_lines(issue)
253
- lines += make_history_lines(issue)
251
+ lines += make_stats_lines(issue: issue, done: done)
252
+ unless done
253
+ lines += make_description_lines(issue)
254
+ lines += make_sprints_lines(issue)
255
+ lines += make_blocked_stalled_lines(issue)
256
+ lines += make_child_lines(issue)
257
+ lines += make_history_lines(issue)
258
+ end
254
259
  lines
255
260
  end
256
261
 
@@ -261,6 +266,8 @@ class DailyView < ChartBase
261
266
  assemble_issue_lines(issue, child: child).each do |row|
262
267
  if row.is_a? Issue
263
268
  result << render_issue(row, child: true)
269
+ elsif row.is_a?(String)
270
+ result << row
264
271
  else
265
272
  result << '<div class="heading">'
266
273
  row.each do |chunk|
@@ -410,14 +410,17 @@ class DataQualityReport < ChartBase
410
410
  def render_status_not_on_board problems
411
411
  <<-HTML
412
412
  #{label_issues problems.size} were not visible on the board for some period of time. This may impact
413
- timings as the work was likely to have been forgotten if it wasn't visible.
413
+ timings as the work was likely to have been forgotten if it wasn't visible. What does "not visible"
414
+ mean in this context? The issue was in a status that is not mapped to any visible column on the board.
415
+ Look in "unmapped statuses" on your board.
414
416
  HTML
415
417
  end
416
418
 
417
419
  def render_created_in_wrong_status problems
418
420
  <<-HTML
419
- #{label_issues problems.size} were created in a status not designated as Backlog. This will impact
420
- the measurement of start times and will therefore impact whether it's shown as in progress or not.
421
+ #{label_issues problems.size} were created in a status that is not considered to be some varient
422
+ of To Do. Most likely this means that the issue was created from one of the columns on the board,
423
+ rather than in the backlog. Why Jira allows this is still a mystery.
421
424
  HTML
422
425
  end
423
426
 
@@ -51,7 +51,10 @@ class DependencyChart < ChartBase
51
51
  instance_eval(&@rules_block) if @rules_block
52
52
 
53
53
  dot_graph = build_dot_graph
54
- return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if dot_graph.nil?
54
+ if dot_graph.nil?
55
+ return "<h1 class='foldable'>#{@header_text}</h1>" \
56
+ '<div>No data matched the selected criteria. Nothing to show.</div>'
57
+ end
55
58
 
56
59
  svg = execute_graphviz(dot_graph.join("\n"))
57
60
  "<h1>#{@header_text}</h1><div>#{@description_text}</div>#{shrink_svg svg}"
@@ -3,8 +3,29 @@
3
3
  require 'cgi'
4
4
  require 'json'
5
5
 
6
+ class DownloadIssueData
7
+ attr_accessor :key, :found_in_primary_query, :last_modified,
8
+ :up_to_date, :cache_path, :issue
9
+
10
+ def initialize(
11
+ key:,
12
+ found_in_primary_query: true,
13
+ last_modified: nil,
14
+ up_to_date: true,
15
+ cache_path: nil,
16
+ issue: nil
17
+ )
18
+ @key = key
19
+ @found_in_primary_query = found_in_primary_query
20
+ @last_modified = last_modified
21
+ @up_to_date = up_to_date
22
+ @cache_path = cache_path
23
+ @issue = issue
24
+ end
25
+ end
26
+
6
27
  class Downloader
7
- CURRENT_METADATA_VERSION = 4
28
+ CURRENT_METADATA_VERSION = 5
8
29
 
9
30
  attr_accessor :metadata
10
31
  attr_reader :file_system
@@ -12,6 +33,15 @@ class Downloader
12
33
  # For testing only
13
34
  attr_reader :start_date_in_query, :board_id_to_filter_id
14
35
 
36
+ def self.create download_config:, file_system:, jira_gateway:
37
+ is_cloud = jira_gateway.settings['jira_cloud'] || jira_gateway.cloud?
38
+ (is_cloud ? DownloaderForCloud : DownloaderForDataCenter).new(
39
+ download_config: download_config,
40
+ file_system: file_system,
41
+ jira_gateway: jira_gateway
42
+ )
43
+ end
44
+
15
45
  def initialize download_config:, file_system:, jira_gateway:
16
46
  @metadata = {}
17
47
  @download_config = download_config
@@ -28,7 +58,6 @@ class Downloader
28
58
  log '', both: true
29
59
  log @download_config.project_config.name, both: true
30
60
 
31
- init_gateway
32
61
  load_metadata
33
62
 
34
63
  if @metadata['no-download']
@@ -50,11 +79,6 @@ class Downloader
50
79
  save_metadata
51
80
  end
52
81
 
53
- def init_gateway
54
- @jira_gateway.load_jira_config(@download_config.project_config.jira_config)
55
- @jira_gateway.ignore_ssl_errors = @download_config.project_config.settings['ignore_ssl_errors']
56
- end
57
-
58
82
  def log text, both: false
59
83
  @file_system.log text, also_write_to_stderr: both
60
84
  end
@@ -66,93 +90,6 @@ class Downloader
66
90
  ids
67
91
  end
68
92
 
69
- def download_issues board:
70
- log " Downloading primary issues for board #{board.id}", both: true
71
- path = File.join(@target_path, "#{file_prefix}_issues/")
72
- unless Dir.exist?(path)
73
- log " Creating path #{path}"
74
- Dir.mkdir(path)
75
- end
76
-
77
- filter_id = @board_id_to_filter_id[board.id]
78
- jql = make_jql(filter_id: filter_id)
79
- jira_search_by_jql(jql: jql, initial_query: true, board: board, path: path)
80
-
81
- log " Downloading linked issues for board #{board.id}", both: true
82
- loop do
83
- @issue_keys_pending_download.reject! { |key| @issue_keys_downloaded_in_current_run.include? key }
84
- break if @issue_keys_pending_download.empty?
85
-
86
- keys_to_request = @issue_keys_pending_download[0..99]
87
- @issue_keys_pending_download.reject! { |key| keys_to_request.include? key }
88
- jql = "key in (#{keys_to_request.join(', ')})"
89
- jira_search_by_jql(jql: jql, initial_query: false, board: board, path: path)
90
- end
91
- end
92
-
93
- def jira_search_by_jql jql:, initial_query:, board:, path:
94
- intercept_jql = @download_config.project_config.settings['intercept_jql']
95
- jql = intercept_jql.call jql if intercept_jql
96
-
97
- log " JQL: #{jql}"
98
- escaped_jql = CGI.escape jql
99
-
100
- if @jira_gateway.cloud?
101
- max_results = 5_000 # The maximum allowed by Jira
102
- next_page_token = nil
103
- issue_count = 0
104
-
105
- loop do
106
- json = @jira_gateway.call_url relative_url: '/rest/api/3/search/jql' \
107
- "?jql=#{escaped_jql}&maxResults=#{max_results}&" \
108
- "nextPageToken=#{next_page_token}&expand=changelog&fields=*all"
109
- next_page_token = json['nextPageToken']
110
-
111
- json['issues'].each do |issue_json|
112
- issue_json['exporter'] = {
113
- 'in_initial_query' => initial_query
114
- }
115
- identify_other_issues_to_be_downloaded raw_issue: issue_json, board: board
116
- file = "#{issue_json['key']}-#{board.id}.json"
117
-
118
- @file_system.save_json(json: issue_json, filename: File.join(path, file))
119
- issue_count += 1
120
- end
121
-
122
- message = " Downloaded #{issue_count} issues"
123
- log message, both: true
124
-
125
- break unless next_page_token
126
- end
127
- else
128
- max_results = 100
129
- start_at = 0
130
- total = 1
131
- while start_at < total
132
- json = @jira_gateway.call_url relative_url: '/rest/api/2/search' \
133
- "?jql=#{escaped_jql}&maxResults=#{max_results}&startAt=#{start_at}&expand=changelog&fields=*all"
134
-
135
- json['issues'].each do |issue_json|
136
- issue_json['exporter'] = {
137
- 'in_initial_query' => initial_query
138
- }
139
- identify_other_issues_to_be_downloaded raw_issue: issue_json, board: board
140
- file = "#{issue_json['key']}-#{board.id}.json"
141
-
142
- @file_system.save_json(json: issue_json, filename: File.join(path, file))
143
- end
144
-
145
- total = json['total'].to_i
146
- max_results = json['maxResults']
147
-
148
- message = " Downloaded #{start_at + 1}-#{[start_at + max_results, total].min} of #{total} issues to #{path} "
149
- log message, both: true
150
-
151
- start_at += json['issues'].size
152
- end
153
- end
154
- end
155
-
156
93
  def identify_other_issues_to_be_downloaded raw_issue:, board:
157
94
  issue = Issue.new raw: raw_issue, board: board
158
95
  @issue_keys_downloaded_in_current_run << issue.key
@@ -178,6 +115,8 @@ class Downloader
178
115
  end
179
116
 
180
117
  def download_users
118
+ return unless @jira_gateway.cloud?
119
+
181
120
  log ' Downloading all users', both: true
182
121
  json = @jira_gateway.call_url relative_url: '/rest/api/2/users'
183
122
 
@@ -327,11 +266,7 @@ class Downloader
327
266
 
328
267
  if start_date
329
268
  @download_date_range = start_date..today.to_date
330
-
331
- # For an incremental download, we want to query from the end of the previous one, not from the
332
- # beginning of the full range.
333
- @start_date_in_query = metadata['date_end'] || @download_date_range.begin
334
- log " Incremental download only. Pulling from #{@start_date_in_query}", both: true if metadata['date_end']
269
+ @start_date_in_query = @download_date_range.begin
335
270
 
336
271
  # Catch-all to pick up anything that's been around since before the range started but hasn't
337
272
  # had an update during the range.