jirametrics 1.5 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/bin/jirametrics +1 -0
  3. data/lib/jirametrics/aggregate_config.rb +2 -1
  4. data/lib/jirametrics/aging_work_bar_chart.rb +3 -3
  5. data/lib/jirametrics/aging_work_in_progress_chart.rb +6 -6
  6. data/lib/jirametrics/anonymizer.rb +3 -3
  7. data/lib/jirametrics/blocked_stalled_change.rb +5 -10
  8. data/lib/jirametrics/board.rb +11 -13
  9. data/lib/jirametrics/chart_base.rb +5 -5
  10. data/lib/jirametrics/cycletime_histogram.rb +2 -2
  11. data/lib/jirametrics/cycletime_scatterplot.rb +5 -2
  12. data/lib/jirametrics/daily_wip_chart.rb +1 -1
  13. data/lib/jirametrics/data_quality_report.rb +4 -4
  14. data/lib/jirametrics/dependency_chart.rb +5 -4
  15. data/lib/jirametrics/download_config.rb +0 -19
  16. data/lib/jirametrics/downloader.rb +32 -75
  17. data/lib/jirametrics/examples/aggregated_project.rb +61 -3
  18. data/lib/jirametrics/examples/standard_project.rb +3 -3
  19. data/lib/jirametrics/expedited_chart.rb +4 -4
  20. data/lib/jirametrics/experimental/generator.rb +5 -4
  21. data/lib/jirametrics/experimental/info.rb +2 -2
  22. data/lib/jirametrics/exporter.rb +16 -30
  23. data/lib/jirametrics/file_system.rb +36 -0
  24. data/lib/jirametrics/groupable_issue_chart.rb +0 -9
  25. data/lib/jirametrics/hierarchy_table.rb +1 -1
  26. data/lib/jirametrics/html_report_config.rb +5 -30
  27. data/lib/jirametrics/issue.rb +4 -8
  28. data/lib/jirametrics/issue_link.rb +0 -7
  29. data/lib/jirametrics/jira_gateway.rb +59 -0
  30. data/lib/jirametrics/project_config.rb +78 -88
  31. data/lib/jirametrics/rules.rb +1 -20
  32. data/lib/jirametrics/sprint_burndown.rb +7 -6
  33. data/lib/jirametrics/sprint_issue_change_data.rb +4 -9
  34. data/lib/jirametrics/status.rb +24 -20
  35. data/lib/jirametrics/status_collection.rb +2 -2
  36. data/lib/jirametrics/story_point_accuracy_chart.rb +2 -7
  37. data/lib/jirametrics/throughput_chart.rb +2 -2
  38. data/lib/jirametrics/trend_line_calculator.rb +4 -4
  39. data/lib/jirametrics/value_equality.rb +23 -0
  40. data/lib/jirametrics.rb +3 -1
  41. metadata +5 -17
  42. data/lib/jirametrics/json_file_loader.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc425ec4e1efcb78c2e12facdbfb0109383c379c1e08a83f46e3587dceabda7a
4
- data.tar.gz: aa98dca9360bec6250a423790f7e1d54d5d3c102804b514468aad220c4913cad
3
+ metadata.gz: 26b8d8955807f88acf9f1cce8443d183a9fb72fce98424aba3c7c29e876a6a38
4
+ data.tar.gz: '039eb9fd005ed0a036f20c72e8f9a8c4cb4bad19f030ed3cf0445bdb6afb0902'
5
5
  SHA512:
6
- metadata.gz: 7ad6615bf254963dabe4557e907f6bacb0340562d9120c9fc3d5ca722b88709531b3e58148d1957707931e88512950053be04c49ace5d531c2990e99cb6e4254
7
- data.tar.gz: 06b0af0ce2512d028bb0d11771610c75d88246113a0bc3cb789a348f4d3e24c745d419802248f4ad25caebf845e682ecabd7aa24b1aa39a911164fe0332d111a
6
+ metadata.gz: ecf2036e99a4bed4275da79e58740bac58c00d8a61120872abb0d51459631d7b7f5c80898bf4bb1212a87042c54cdb83ace95c1f86d2f2df43b8611626bbf1a0
7
+ data.tar.gz: 5f01cabf487497389977fab8d341d5173e3c5835c8e42422d232aa3ba5b8aaa40af0cb975235374bf953cecf45b2615c5f6b6391560fcdcf6c0f1d77d6ae9e3f
data/bin/jirametrics CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'jirametrics'
4
5
  JiraMetrics.start(ARGV)
@@ -48,7 +48,7 @@ class AggregateConfig
48
48
  @project_config.jira_url = project.jira_url if @project_config.jira_url.nil?
49
49
  unless @project_config.jira_url == project.jira_url
50
50
  raise 'Not allowed to aggregate projects from different Jira instances: ' \
51
- "#{@project_config.jira_url.inspect} and #{project.jira_url.inspect}"
51
+ "#{@project_config.jira_url.inspect} and #{project.jira_url.inspect}. For project #{project_name}"
52
52
  end
53
53
 
54
54
  @included_projects << project
@@ -93,6 +93,7 @@ class AggregateConfig
93
93
  end
94
94
 
95
95
  raise "Can't calculate range" if earliest.nil? || latest.nil?
96
+
96
97
  earliest..latest
97
98
  end
98
99
  end
@@ -185,14 +185,14 @@ class AgingWorkBarChart < ChartBase
185
185
  end
186
186
 
187
187
  def data_set_by_block(
188
- issue:, issue_label:, title_label:, stack:, color:, start_date:, end_date: date_range.end, &block
188
+ issue:, issue_label:, title_label:, stack:, color:, start_date:, end_date: date_range.end
189
189
  )
190
190
  started = nil
191
191
  ended = nil
192
192
  data = []
193
193
 
194
194
  (start_date..end_date).each do |day|
195
- if block.call(day)
195
+ if yield(day)
196
196
  started = day if started.nil?
197
197
  ended = day
198
198
  elsif ended
@@ -225,7 +225,7 @@ class AgingWorkBarChart < ChartBase
225
225
  end
226
226
 
227
227
  def calculate_percent_line percentage: 85
228
- days = completed_issues_in_range.collect { |issue| issue.board.cycletime.cycletime(issue) }.compact.sort
228
+ days = completed_issues_in_range.filter_map { |issue| issue.board.cycletime.cycletime(issue) }.sort
229
229
  return nil if days.empty?
230
230
 
231
231
  days[days.length * percentage / 100]
@@ -67,7 +67,7 @@ class AgingWorkInProgressChart < ChartBase
67
67
  {
68
68
  'type' => 'line',
69
69
  'label' => rules.label,
70
- 'data' => rules_to_issues[rules].collect do |issue|
70
+ 'data' => rules_to_issues[rules].filter_map do |issue|
71
71
  age = issue.board.cycletime.age(issue, today: date_range.end)
72
72
  column = column_for issue: issue
73
73
  next if column.nil?
@@ -76,7 +76,7 @@ class AgingWorkInProgressChart < ChartBase
76
76
  'x' => column.name,
77
77
  'title' => ["#{issue.key} : #{issue.summary} (#{label_days age})"]
78
78
  }
79
- end.compact,
79
+ end,
80
80
  'fill' => false,
81
81
  'showLine' => false,
82
82
  'backgroundColor' => rules.color
@@ -101,16 +101,16 @@ class AgingWorkInProgressChart < ChartBase
101
101
 
102
102
  def accumulated_status_ids_per_column
103
103
  accumulated_status_ids = []
104
- @board_columns.reverse.collect do |column|
104
+ @board_columns.reverse.filter_map do |column|
105
105
  next if column == @fake_column
106
106
 
107
107
  accumulated_status_ids += column.status_ids
108
108
  [column.name, accumulated_status_ids.dup]
109
- end.compact.reverse
109
+ end.reverse
110
110
  end
111
111
 
112
112
  def ages_of_issues_that_crossed_column_boundary issues:, status_ids:
113
- issues.collect do |issue|
113
+ issues.filter_map do |issue|
114
114
  stop = issue.first_time_in_status(*status_ids)
115
115
  start = issue.board.cycletime.started_time(issue)
116
116
 
@@ -119,7 +119,7 @@ class AgingWorkInProgressChart < ChartBase
119
119
  next if stop < start
120
120
 
121
121
  (stop.to_date - start.to_date).to_i + 1
122
- end.compact
122
+ end
123
123
  end
124
124
 
125
125
  def column_for issue:
@@ -28,7 +28,7 @@ class Anonymizer
28
28
  # just try again. In every case we've seen, it's worked on the second attempt, but we'll be
29
29
  # cautious and try five times.
30
30
  5.times do |i|
31
- return RandomWord.phrases.next.gsub(/_/, ' ')
31
+ return RandomWord.phrases.next.tr('_', ' ')
32
32
  rescue # rubocop:disable Style/RescueStandardError We don't care what exception was thrown.
33
33
  puts "Random word blew up on attempt #{i + 1}"
34
34
  end
@@ -45,7 +45,7 @@ class Anonymizer
45
45
 
46
46
  issue.issue_links.each do |link|
47
47
  other_issue = link.other_issue
48
- next if other_issue.key =~ /^ANON-\d+$/ # Already anonymized?
48
+ next if other_issue.key.match?(/^ANON-\d+$/) # Already anonymized?
49
49
 
50
50
  other_issue.raw['key'] = "ANON-#{counter += 1}"
51
51
  other_issue.raw['fields']['summary'] = random_phrase
@@ -179,7 +179,7 @@ class Anonymizer
179
179
  end
180
180
 
181
181
  def anonymize_board_names
182
- @all_boards.values.each do |board|
182
+ @all_boards.each_value do |board|
183
183
  board.raw['name'] = "#{random_phrase} board"
184
184
  end
185
185
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'jirametrics/value_equality'
4
+
3
5
  class BlockedStalledChange
6
+ include ValueEquality
4
7
  attr_reader :time, :blocking_issue_keys, :flag, :status, :stalled_days, :status_is_blocking
5
8
 
6
9
  def initialize time:, flagged: nil, status: nil, status_is_blocking: true, blocking_issue_keys: nil, stalled_days: nil
@@ -16,16 +19,8 @@ class BlockedStalledChange
16
19
  def stalled? = @stalled_days || stalled_by_status?
17
20
  def active? = !blocked? && !stalled?
18
21
 
19
- def blocked_by_status? = (@status && @status_is_blocking)
20
- def stalled_by_status? = (@status && !@status_is_blocking)
21
-
22
- def ==(other)
23
- (other.class == self.class) && (other.state == state)
24
- end
25
-
26
- def state
27
- instance_variables.map { |variable| instance_variable_get variable }
28
- end
22
+ def blocked_by_status? = @status && @status_is_blocking
23
+ def stalled_by_status? = @status && !@status_is_blocking
29
24
 
30
25
  def reasons
31
26
  result = []
@@ -17,15 +17,10 @@ class Board
17
17
  # visible on the board. If the board is configured to have a kanban backlog then it will have
18
18
  # statuses matched to it and otherwise, there will be no statuses.
19
19
  if kanban?
20
- assert_jira_behaviour_true(columns[0]['name'] == 'Backlog') do
21
- "Expected first column to be called Backlog: #{raw}"
22
- end
23
-
24
20
  @backlog_statuses = @possible_statuses.expand_statuses(status_ids_from_column columns[0]) do |unknown_status|
25
- # Yet another "theoretically impossible and yet we've seen it in production" moment
26
- puts "Status #{unknown_status.inspect} is defined as being in the backlog for board #{name.inspect}:#{id} " \
27
- 'and yet it\'s not defined in the list of possible statuses available to the project. Check your Jira ' \
28
- 'configuration'
21
+ # There is a status defined as being 'backlog' that is no longer being returned in statuses.
22
+ # We used to display a warning for this but honestly, there is nothing that anyone can do about it
23
+ # so now we just quietly ignore it.
29
24
  end
30
25
  columns = columns[1..]
31
26
  else
@@ -33,10 +28,10 @@ class Board
33
28
  @backlog_statuses = []
34
29
  end
35
30
 
36
- @visible_columns = columns.collect do |column|
31
+ @visible_columns = columns.filter_map do |column|
37
32
  # It's possible for a column to be defined without any statuses and in this case, it won't be visible.
38
33
  BoardColumn.new column unless status_ids_from_column(column).empty?
39
- end.compact
34
+ end
40
35
  end
41
36
 
42
37
  def server_url_prefix
@@ -51,7 +46,7 @@ class Board
51
46
  end
52
47
 
53
48
  def status_ids_from_column column
54
- column['statuses'].collect { |status| status['id'].to_i }
49
+ column['statuses']&.collect { |status| status['id'].to_i } || []
55
50
  end
56
51
 
57
52
  def status_ids_in_or_right_of_column column_name
@@ -65,7 +60,7 @@ class Board
65
60
  end
66
61
 
67
62
  unless found_it
68
- column_names = @visible_columns.collect(&:name).collect(&:inspect).join(', ')
63
+ column_names = @visible_columns.collect { |c| c.name.inspect }.join(', ')
69
64
  raise "No visible column with name: #{column_name.inspect} Possible options are: #{column_names}"
70
65
  end
71
66
  status_ids
@@ -84,7 +79,10 @@ class Board
84
79
  end
85
80
 
86
81
  def project_id
87
- @raw['location']&.[]('id')
82
+ location = @raw['location']
83
+ return nil unless location
84
+
85
+ location['id'] if location['type'] == 'project'
88
86
  end
89
87
 
90
88
  def name
@@ -46,7 +46,7 @@ class ChartBase
46
46
 
47
47
  # Render the file and then wrap it with standard headers and quality checks.
48
48
  def wrap_and_render caller_binding, file
49
- result = String.new
49
+ result = +''
50
50
  result << "<h1>#{@header_text}</h1>" if @header_text
51
51
  result << ERB.new(@description_text).result(caller_binding) if @description_text
52
52
  result << render(caller_binding, file)
@@ -74,7 +74,7 @@ class ChartBase
74
74
  type: 'bar',
75
75
  label: label,
76
76
  data: date_issues_list.collect do |date, issues|
77
- issues.sort! { |a, b| a.key_as_i <=> b.key_as_i }
77
+ issues.sort_by!(&:key_as_i)
78
78
  title = "#{label} (#{label_issues issues.size})"
79
79
  {
80
80
  x: date,
@@ -189,7 +189,7 @@ class ChartBase
189
189
  begin
190
190
  statuses = board.possible_statuses.expand_statuses([name_or_id])
191
191
  rescue RuntimeError => e
192
- return "<span style='color: red'>#{name_or_id}</span>" if e.message =~ /^Status not found:/
192
+ return "<span style='color: red'>#{name_or_id}</span>" if e.message.match?(/^Status not found:/)
193
193
 
194
194
  throw e
195
195
  end
@@ -212,7 +212,7 @@ class ChartBase
212
212
  end
213
213
 
214
214
  def random_color
215
- "\##{Random.bytes(3).unpack1('H*')}"
215
+ "##{Random.bytes(3).unpack1('H*')}"
216
216
  end
217
217
 
218
218
  def canvas width:, height:, responsive: true
@@ -233,7 +233,7 @@ class ChartBase
233
233
  @issues = issues
234
234
  return unless @filter_issues_block
235
235
 
236
- @issues = issues.collect { |i| @filter_issues_block.call(i) }.compact.uniq
236
+ @issues = issues.filter_map { |i| @filter_issues_block.call(i) }.uniq
237
237
  puts @issues.collect(&:key).join(', ')
238
238
  end
239
239
  end
@@ -58,7 +58,7 @@ class CycletimeHistogram < ChartBase
58
58
  {
59
59
  type: 'bar',
60
60
  label: label,
61
- data: keys.sort.collect do |key|
61
+ data: keys.sort.filter_map do |key|
62
62
  next if histogram_data[key].zero?
63
63
 
64
64
  {
@@ -66,7 +66,7 @@ class CycletimeHistogram < ChartBase
66
66
  y: histogram_data[key],
67
67
  title: "#{histogram_data[key]} items completed in #{label_days key}"
68
68
  }
69
- end.compact,
69
+ end,
70
70
  backgroundColor: color,
71
71
  borderRadius: 0
72
72
  }
@@ -61,7 +61,7 @@ class CycletimeScatterplot < ChartBase
61
61
  label = rules.label
62
62
  color = rules.color
63
63
  percent_line = calculate_percent_line completed_issues_by_type
64
- data = completed_issues_by_type.collect { |issue| data_for_issue(issue) }.compact
64
+ data = completed_issues_by_type.filter_map { |issue| data_for_issue(issue) }
65
65
  data_sets << {
66
66
  label: "#{label} (85% at #{label_days(percent_line)})",
67
67
  data: data,
@@ -88,7 +88,10 @@ class CycletimeScatterplot < ChartBase
88
88
 
89
89
  # The trend calculation works with numbers only so convert Time to an int and back
90
90
  calculator = TrendLineCalculator.new(points)
91
- data_points = calculator.chart_datapoints range: time_range.begin.to_i..time_range.end.to_i, max_y: @highest_cycletime
91
+ data_points = calculator.chart_datapoints(
92
+ range: time_range.begin.to_i..time_range.end.to_i,
93
+ max_y: @highest_cycletime
94
+ )
92
95
  data_points.each do |point_hash|
93
96
  point_hash[:x] = chart_format Time.at(point_hash[:x])
94
97
  end
@@ -50,7 +50,7 @@ class DailyWipChart < ChartBase
50
50
  def select_possible_rules issue_rules_by_active_date
51
51
  possible_rules = []
52
52
  issue_rules_by_active_date.each_pair do |_date, issues_rules_list|
53
- issues_rules_list.each do |_issue, rules|
53
+ issues_rules_list.each do |_issue, rules| # rubocop:disable Style/HashEachMethods
54
54
  possible_rules << rules unless possible_rules.any? { |r| r.group == rules.group }
55
55
  end
56
56
  end
@@ -83,7 +83,7 @@ class DataQualityReport < ChartBase
83
83
  end
84
84
 
85
85
  def initialize_entries
86
- @entries = @issues.collect do |issue|
86
+ @entries = @issues.filter_map do |issue|
87
87
  cycletime = issue.board.cycletime
88
88
  started = cycletime.started_time(issue)
89
89
  stopped = cycletime.stopped_time(issue)
@@ -91,7 +91,7 @@ class DataQualityReport < ChartBase
91
91
  next if started && started > time_range.end
92
92
 
93
93
  Entry.new started: started, stopped: stopped, issue: issue
94
- end.compact
94
+ end
95
95
 
96
96
  @entries.sort! do |a, b|
97
97
  a.issue.key =~ /.+-(\d+)$/
@@ -107,11 +107,11 @@ class DataQualityReport < ChartBase
107
107
  def scan_for_completed_issues_without_a_start_time entry:
108
108
  return unless entry.stopped && entry.started.nil?
109
109
 
110
- status_names = entry.issue.changes.collect do |change|
110
+ status_names = entry.issue.changes.filter_map do |change|
111
111
  next unless change.status?
112
112
 
113
113
  format_status change.value, board: entry.issue.board
114
- end.compact
114
+ end
115
115
 
116
116
  entry.report(
117
117
  problem_key: :completed_but_not_started,
@@ -78,7 +78,7 @@ class DependencyChart < ChartBase
78
78
  end
79
79
 
80
80
  def make_dot_link issue_link:, link_rules:
81
- result = String.new
81
+ result = +''
82
82
  result << issue_link.origin.key.inspect
83
83
  result << ' -> '
84
84
  result << issue_link.other_issue.key.inspect
@@ -91,11 +91,11 @@ class DependencyChart < ChartBase
91
91
  end
92
92
 
93
93
  def make_dot_issue issue:, issue_rules:
94
- result = String.new
94
+ result = +''
95
95
  result << issue.key.inspect
96
96
  result << '['
97
97
  label = issue_rules.label || "#{issue.key}|#{issue.type}"
98
- label = label.inspect unless label =~ /^<.+>$/
98
+ label = label.inspect unless label.match?(/^<.+>$/)
99
99
  result << "label=#{label}"
100
100
  result << ',shape=Mrecord'
101
101
  tooltip = "#{issue.key}: #{issue.summary}"
@@ -160,6 +160,7 @@ class DependencyChart < ChartBase
160
160
  dot_graph << '}'
161
161
 
162
162
  return nil if visible_issues.empty?
163
+
163
164
  dot_graph
164
165
  end
165
166
 
@@ -205,7 +206,7 @@ class DependencyChart < ChartBase
205
206
  line.gsub!(/[{<]/, '[')
206
207
  line.gsub!(/[}>]/, ']')
207
208
  line.gsub!(/\s*&\s*/, ' and ')
208
- line.gsub!('|', '')
209
+ line.delete!('|')
209
210
 
210
211
  if line.length > max_width
211
212
  line.gsub(/(.{1,#{max_width}})(\s+|$)/, "\\1#{separator}").strip
@@ -15,25 +15,6 @@ class DownloadConfig
15
15
  instance_eval(&@block)
16
16
  end
17
17
 
18
- def project_key _key = nil
19
- raise 'project, filter, and jql directives are no longer supported. See ' \
20
- 'https://github.com/mikebowler/jira-export/wiki/Deprecated#project-filter-and-jql-are-no-longer-supported-in-the-download-section'
21
- end
22
-
23
- def board_ids *ids
24
- deprecated message: 'board_ids in the download block are deprecated. See https://github.com/mikebowler/jira-export/wiki/Deprecated'
25
- @board_ids = ids unless ids.empty?
26
- @board_ids
27
- end
28
-
29
- def filter_name _filter = nil
30
- project_key
31
- end
32
-
33
- def jql _query = nil
34
- project_key
35
- end
36
-
37
18
  def rolling_date_count count = nil
38
19
  @rolling_date_count = count unless count.nil?
39
20
  @rolling_date_count
@@ -2,21 +2,22 @@
2
2
 
3
3
  require 'cgi'
4
4
  require 'json'
5
- require 'English'
6
5
 
7
6
  class Downloader
8
7
  CURRENT_METADATA_VERSION = 4
9
8
 
10
- attr_accessor :metadata, :quiet_mode, :logfile, :logfile_name
9
+ attr_accessor :metadata, :quiet_mode
10
+ attr_reader :file_system
11
11
 
12
12
  # For testing only
13
13
  attr_reader :start_date_in_query
14
14
 
15
- def initialize download_config:, json_file_loader: JsonFileLoader.new
15
+ def initialize download_config:, file_system:, jira_gateway:
16
16
  @metadata = {}
17
17
  @download_config = download_config
18
18
  @target_path = @download_config.project_config.target_path
19
- @json_file_loader = json_file_loader
19
+ @file_system = file_system
20
+ @jira_gateway = jira_gateway
20
21
  @board_id_to_filter_id = {}
21
22
 
22
23
  @issue_keys_downloaded_in_current_run = []
@@ -27,7 +28,7 @@ class Downloader
27
28
  log '', both: true
28
29
  log @download_config.project_config.name, both: true
29
30
 
30
- load_jira_config(@download_config.project_config.jira_config)
31
+ init_gateway
31
32
  load_metadata
32
33
 
33
34
  if @metadata['no-download']
@@ -47,60 +48,23 @@ class Downloader
47
48
  save_metadata
48
49
  end
49
50
 
51
+ def init_gateway
52
+ @jira_gateway.load_jira_config(@download_config.project_config.jira_config)
53
+ @jira_gateway.ignore_ssl_errors = @download_config.project_config.settings['ignore_ssl_errors']
54
+ end
55
+
50
56
  def log text, both: false
51
- @logfile&.puts text
57
+ @file_system.log text
52
58
  puts text if both
53
59
  end
54
60
 
55
61
  def find_board_ids
56
62
  ids = @download_config.project_config.board_configs.collect(&:id)
57
- if ids.empty?
58
- deprecated message: 'board_ids in the download block have been deprecated. See https://github.com/mikebowler/jira-export/wiki/Deprecated'
59
- ids = @download_config.board_ids
60
- end
61
63
  raise 'Board ids must be specified' if ids.empty?
62
64
 
63
65
  ids
64
66
  end
65
67
 
66
- def load_jira_config jira_config
67
- @jira_url = jira_config['url']
68
- @jira_email = jira_config['email']
69
- @jira_api_token = jira_config['api_token']
70
- @jira_personal_access_token = jira_config['personal_access_token']
71
-
72
- raise 'When specifying an api-token, you must also specify email' if @jira_api_token && !@jira_email
73
-
74
- if @jira_api_token && @jira_personal_access_token
75
- raise "You can't specify both an api-token and a personal-access-token. They don't work together."
76
- end
77
-
78
- @cookies = (jira_config['cookies'] || []).collect { |key, value| "#{key}=#{value}" }.join(';')
79
- end
80
-
81
- def call_command command
82
- log " #{command.gsub(/\s+/, ' ')}"
83
- result = `#{command}`
84
- log result unless $CHILD_STATUS.success?
85
- return result if $CHILD_STATUS.success?
86
-
87
- log "Failed call with exit status #{$CHILD_STATUS.exitstatus}. See #{@logfile_name} for details", both: true
88
- exit $CHILD_STATUS.exitstatus
89
- end
90
-
91
- def make_curl_command url:
92
- command = 'curl'
93
- command += ' -s'
94
- command += ' -k' if @download_config.project_config.settings['ignore_ssl_errors']
95
- command += " --cookie #{@cookies.inspect}" unless @cookies.empty?
96
- command += " --user #{@jira_email}:#{@jira_api_token}" if @jira_api_token
97
- command += " -H \"Authorization: Bearer #{@jira_personal_access_token}\"" if @jira_personal_access_token
98
- command += ' --request GET'
99
- command += ' --header "Accept: application/json"'
100
- command += " --url \"#{url}\""
101
- command
102
- end
103
-
104
68
  def download_issues board_id:
105
69
  log " Downloading primary issues for board #{board_id}", both: true
106
70
  path = "#{@target_path}#{@download_config.project_config.file_prefix}_issues/"
@@ -136,10 +100,9 @@ class Downloader
136
100
  start_at = 0
137
101
  total = 1
138
102
  while start_at < total
139
- command = make_curl_command url: "#{@jira_url}/rest/api/2/search" \
103
+ json = @jira_gateway.call_url relative_url: '/rest/api/2/search' \
140
104
  "?jql=#{escaped_jql}&maxResults=#{max_results}&startAt=#{start_at}&expand=changelog&fields=*all"
141
105
 
142
- json = JSON.parse call_command(command)
143
106
  exit_if_call_failed json
144
107
 
145
108
  json['issues'].each do |issue_json|
@@ -148,7 +111,8 @@ class Downloader
148
111
  }
149
112
  identify_other_issues_to_be_downloaded issue_json
150
113
  file = "#{issue_json['key']}-#{board_id}.json"
151
- write_json(issue_json, File.join(path, file))
114
+
115
+ @file_system.save_json(json: issue_json, filename: File.join(path, file))
152
116
  end
153
117
 
154
118
  total = json['total'].to_i
@@ -193,24 +157,23 @@ class Downloader
193
157
 
194
158
  def download_statuses
195
159
  log ' Downloading all statuses', both: true
196
- command = make_curl_command url: "\"#{@jira_url}/rest/api/2/status\""
197
- json = JSON.parse call_command(command)
160
+ json = @jira_gateway.call_url relative_url: "/rest/api/2/status"
198
161
 
199
- write_json json, "#{@target_path}#{@download_config.project_config.file_prefix}_statuses.json"
162
+ @file_system.save_json(
163
+ json: json,
164
+ filename: "#{@target_path}#{@download_config.project_config.file_prefix}_statuses.json"
165
+ )
200
166
  end
201
167
 
202
168
  def download_board_configuration board_id:
203
169
  log " Downloading board configuration for board #{board_id}", both: true
204
- command = make_curl_command url: "#{@jira_url}/rest/agile/1.0/board/#{board_id}/configuration"
205
-
206
- json = JSON.parse call_command(command)
170
+ json = @jira_gateway.call_url relative_url: "/rest/agile/1.0/board/#{board_id}/configuration"
207
171
  exit_if_call_failed json
208
172
 
209
173
  @board_id_to_filter_id[board_id] = json['filter']['id'].to_i
210
- # @board_configuration = json if @download_config.board_ids.size == 1
211
174
 
212
175
  file_prefix = @download_config.project_config.file_prefix
213
- write_json json, "#{@target_path}#{file_prefix}_board_#{board_id}_configuration.json"
176
+ @file_system.save_json json: json, filename: "#{@target_path}#{file_prefix}_board_#{board_id}_configuration.json"
214
177
 
215
178
  download_sprints board_id: board_id if json['type'] == 'scrum'
216
179
  end
@@ -223,34 +186,28 @@ class Downloader
223
186
  is_last = false
224
187
 
225
188
  while is_last == false
226
- command = make_curl_command url: "#{@jira_url}/rest/agile/1.0/board/#{board_id}/sprint?" \
189
+ json = @jira_gateway.call_url relative_url: "/rest/agile/1.0/board/#{board_id}/sprint?" \
227
190
  "maxResults=#{max_results}&startAt=#{start_at}"
228
- json = JSON.parse call_command(command)
229
191
  exit_if_call_failed json
230
192
 
231
- write_json json, "#{@target_path}#{file_prefix}_board_#{board_id}_sprints_#{start_at}.json"
193
+ @file_system.save_json(
194
+ json: json,
195
+ filename: "#{@target_path}#{file_prefix}_board_#{board_id}_sprints_#{start_at}.json"
196
+ )
232
197
  is_last = json['isLast']
233
198
  max_results = json['maxResults']
234
199
  start_at += json['values'].size
235
200
  end
236
201
  end
237
202
 
238
- def write_json json, filename
239
- file_path = File.dirname(filename)
240
- FileUtils.mkdir_p file_path unless File.exist?(file_path)
241
-
242
- File.write(filename, JSON.pretty_generate(json))
243
- end
244
-
245
203
  def metadata_pathname
246
204
  "#{@target_path}#{@download_config.project_config.file_prefix}_meta.json"
247
205
  end
248
206
 
249
207
  def load_metadata
250
208
  # If we've never done a download before then this file won't be there. That's ok.
251
- return unless File.exist? metadata_pathname
252
-
253
- hash = JSON.parse(File.read metadata_pathname)
209
+ hash = file_system.load_json(metadata_pathname, fail_on_error: false)
210
+ return if hash.nil?
254
211
 
255
212
  # Only use the saved metadata if the version number is the same one that we're currently using.
256
213
  # If the cached data is in an older format then we're going to throw most of it away.
@@ -283,13 +240,13 @@ class Downloader
283
240
 
284
241
  @metadata['jira_url'] = @jira_url
285
242
 
286
- write_json @metadata, metadata_pathname
243
+ @file_system.save_json json: @metadata, filename: metadata_pathname
287
244
  end
288
245
 
289
246
  def remove_old_files
290
247
  file_prefix = @download_config.project_config.file_prefix
291
248
  Dir.foreach @target_path do |file|
292
- next unless file =~ /^#{file_prefix}_\d+\.json$/
249
+ next unless file.match?(/^#{file_prefix}_\d+\.json$/)
293
250
 
294
251
  File.unlink "#{@target_path}#{file}"
295
252
  end
@@ -301,7 +258,7 @@ class Downloader
301
258
  return unless File.exist? path
302
259
 
303
260
  Dir.foreach path do |file|
304
- next unless file =~ /\.json$/
261
+ next unless file.match?(/\.json$/)
305
262
 
306
263
  File.unlink File.join(path, file)
307
264
  end