jirametrics 2.2.1 → 2.3

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +13 -25
  3. data/lib/jirametrics/aging_work_bar_chart.rb +57 -39
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +1 -1
  5. data/lib/jirametrics/aging_work_table.rb +9 -26
  6. data/lib/jirametrics/board_config.rb +2 -2
  7. data/lib/jirametrics/chart_base.rb +27 -39
  8. data/lib/jirametrics/cycletime_histogram.rb +1 -1
  9. data/lib/jirametrics/cycletime_scatterplot.rb +1 -1
  10. data/lib/jirametrics/daily_wip_by_age_chart.rb +1 -1
  11. data/lib/jirametrics/daily_wip_chart.rb +1 -13
  12. data/lib/jirametrics/dependency_chart.rb +1 -1
  13. data/lib/jirametrics/{story_point_accuracy_chart.rb → estimate_accuracy_chart.rb} +31 -25
  14. data/lib/jirametrics/examples/standard_project.rb +1 -1
  15. data/lib/jirametrics/expedited_chart.rb +3 -1
  16. data/lib/jirametrics/exporter.rb +2 -2
  17. data/lib/jirametrics/file_config.rb +5 -7
  18. data/lib/jirametrics/file_system.rb +11 -2
  19. data/lib/jirametrics/groupable_issue_chart.rb +2 -4
  20. data/lib/jirametrics/hierarchy_table.rb +4 -4
  21. data/lib/jirametrics/html/aging_work_table.erb +3 -3
  22. data/lib/jirametrics/html_report_config.rb +61 -74
  23. data/lib/jirametrics/issue.rb +70 -39
  24. data/lib/jirametrics/project_config.rb +12 -6
  25. data/lib/jirametrics/sprint_burndown.rb +11 -0
  26. data/lib/jirametrics/status_collection.rb +4 -1
  27. data/lib/jirametrics/throughput_chart.rb +1 -1
  28. data/lib/jirametrics.rb +1 -1
  29. metadata +5 -7
  30. data/lib/jirametrics/experimental/generator.rb +0 -210
  31. data/lib/jirametrics/experimental/info.rb +0 -77
  32. /data/lib/jirametrics/html/{story_point_accuracy_chart.erb → estimate_accuracy_chart.erb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08f0ccb466afa1cba17db109091ab8333b52ed1ec8c1d0b0920c400052f43e7
4
- data.tar.gz: f127c794e8fc4c447cab24cd7d2dc149c17dfec6742c6238ed8c5a3f7faeabcb
3
+ metadata.gz: 2e7264aee468cd14d42fa1c7ba8f737177ba99a78b3f7250d46771f687d4d619
4
+ data.tar.gz: da813f37b2a6047d6ab844f483008ecdb8c579786bd7bba7f2c01f038a259949
5
5
  SHA512:
6
- metadata.gz: 6e07a75c020a54dc077d25b2db1742fc143a4174d5b453813d0d2f3969984f1fc8eb6af18cfa092ecf8ef9d07a4754f9b6013e39c170ee2c3b34122aebf0a387
7
- data.tar.gz: abddaf6fa818847dc6427df90c919c6ade29cf79735c0a2fd64a31b7815d730e146a695fa0696b8896c60f66828dc16c207852a8c116db7fdabc21c90e429964
6
+ metadata.gz: c234046627a2c92729c346f44cf78471e74545e01ce3a9810c6c22ed659f6479a0146303ac7fb0652512feafcf8077025577d2a4c039dddf2fd70d6899360adb
7
+ data.tar.gz: 81f24c30b8d56f8811ed298ca17cebcee6cb32afdbea5cfd12332587aabaa2701b023dc620b651e111226bf66bac9285f84a893549c00c45bd0eb992694a2c64
@@ -19,14 +19,15 @@ class AggregateConfig
19
19
  raise "#{@project_config.name}: When aggregating, you must include at least one other project"
20
20
  end
21
21
 
22
- # If the date range wasn't set then calculate it now
22
+ # If the time range wasn't set then calculate it now
23
23
  @project_config.time_range = find_time_range projects: @included_projects if @project_config.time_range.nil?
24
24
 
25
- adjust_issue_links
25
+ adjust_issue_links issues: @project_config.issues
26
26
  end
27
27
 
28
- def adjust_issue_links
29
- issues = @project_config.issues
28
+ # IssueLinks just have a reference to the key. Walk through all of them to see if we have a full
29
+ # issue that we'd already loaded. If we do, then replace it.
30
+ def adjust_issue_links issues:
30
31
  issues.each do |issue|
31
32
  issue.issue_links.each do |link|
32
33
  other_issue_key = link.other_issue.key
@@ -40,7 +41,7 @@ class AggregateConfig
40
41
  def include_issues_from project_name
41
42
  project = @project_config.exporter.project_configs.find { |p| p.name == project_name }
42
43
  if project.nil?
43
- puts "Warning: Aggregated project #{@project_config.name.inspect} is attempting to load " \
44
+ log "Warning: Aggregated project #{@project_config.name.inspect} is attempting to load " \
44
45
  "project #{project_name.inspect} but it can't be found. Is it disabled?"
45
46
  return
46
47
  end
@@ -57,30 +58,13 @@ class AggregateConfig
57
58
  else
58
59
  issues = project.file_configs.first.issues
59
60
  if project.file_configs.size > 1
60
- puts 'More than one file section is defined. For the aggregated view, we always use ' \
61
+ log 'More than one file section is defined. For the aggregated view, we always use ' \
61
62
  'the first file section'
62
63
  end
63
64
  end
64
65
  @project_config.add_issues issues
65
66
  end
66
67
 
67
- def date_range range
68
- @project_config.time_range = date_range_to_time_range(
69
- date_range: range, timezone_offset: project_config.exporter.timezone_offset
70
- )
71
- end
72
-
73
- def date_range_to_time_range date_range:, timezone_offset:
74
- start_of_first_day = Time.new(
75
- date_range.begin.year, date_range.begin.month, date_range.begin.day, 0, 0, 0, timezone_offset
76
- )
77
- end_of_last_day = Time.new(
78
- date_range.end.year, date_range.end.month, date_range.end.day, 23, 59, 59, timezone_offset
79
- )
80
-
81
- start_of_first_day..end_of_last_day
82
- end
83
-
84
68
  def find_time_range projects:
85
69
  raise "Can't calculate aggregated range as no projects were included." if projects.empty?
86
70
 
@@ -92,8 +76,12 @@ class AggregateConfig
92
76
  latest = range.end if latest.nil? || range.end > latest
93
77
  end
94
78
 
95
- raise "Can't calculate range" if earliest.nil? || latest.nil?
96
-
97
79
  earliest..latest
98
80
  end
81
+
82
+ private
83
+
84
+ def log message
85
+ @project_config.exporter.file_system.log message
86
+ end
99
87
  end
@@ -5,7 +5,7 @@ require 'jirametrics/chart_base'
5
5
  class AgingWorkBarChart < ChartBase
6
6
  @@next_id = 0
7
7
 
8
- def initialize block = nil
8
+ def initialize block
9
9
  super()
10
10
 
11
11
  header_text 'Aging Work Bar Chart'
@@ -27,61 +27,77 @@ class AgingWorkBarChart < ChartBase
27
27
  <li>The bottom bar indicated #{color_block '--expedited-color'} expedited.</li>
28
28
  </ol>
29
29
  </p>
30
- #{ describe_non_working_days }
30
+ #{describe_non_working_days}
31
31
  HTML
32
32
 
33
33
  # Because this one will size itself as needed, we start with a smaller default size
34
34
  @canvas_height = 80
35
35
 
36
- instance_eval(&block) if block
36
+ instance_eval(&block)
37
37
  end
38
38
 
39
39
  def run
40
- aging_issues = @issues.select do |issue|
41
- cycletime = issue.board.cycletime
42
- cycletime.started_time(issue) && cycletime.stopped_time(issue).nil?
43
- end
44
-
45
- grow_chart_height_if_too_many_issues aging_issues.size
40
+ aging_issues = select_aging_issues issues: @issues
46
41
 
47
42
  today = date_range.end
48
- aging_issues.sort! do |a, b|
49
- a.board.cycletime.age(b, today: today) <=> b.board.cycletime.age(a, today: today)
50
- end
51
- data_sets = []
52
- aging_issues.each do |issue|
53
- cycletime = issue.board.cycletime
54
- issue_start_time = cycletime.started_time(issue)
55
- issue_start_date = issue_start_time.to_date
56
- issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
57
- [
58
- status_data_sets(issue: issue, label: issue_label, today: today),
59
- blocked_data_sets(
60
- issue: issue,
61
- issue_label: issue_label,
62
- stack: 'blocked',
63
- issue_start_time: issue_start_time
64
- ),
65
- data_set_by_block(
66
- issue: issue,
67
- issue_label: issue_label,
68
- title_label: 'Expedited',
69
- stack: 'expedited',
70
- color: CssVariable['--expedited-color'],
71
- start_date: issue_start_date
72
- ) { |day| issue.expedited_on_date?(day) }
73
- ].compact.flatten.each do |data|
74
- data_sets << data
75
- end
76
- end
43
+ sort_by_age! issues: aging_issues, today: today
44
+
45
+ grow_chart_height_if_too_many_issues aging_issue_count: aging_issues.size
46
+
47
+ data_sets = aging_issues
48
+ .collect { |issue| data_sets_for_one_issue issue: issue, today: today }
49
+ .flatten
50
+ .compact
77
51
 
78
52
  percentage = calculate_percent_line
79
53
  percentage_line_x = date_range.end - calculate_percent_line if percentage
80
54
 
55
+ if aging_issues.empty?
56
+ @description_text = "<p>There is no aging work</p>"
57
+ return render_top_text(binding) #if aging_issues.empty?
58
+ end
59
+
81
60
  wrap_and_render(binding, __FILE__)
82
61
  end
83
62
 
84
- def grow_chart_height_if_too_many_issues aging_issue_count
63
+ def data_sets_for_one_issue issue:, today:
64
+ cycletime = issue.board.cycletime
65
+ issue_start_time = cycletime.started_time(issue)
66
+ issue_start_date = issue_start_time.to_date
67
+ issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
68
+ [
69
+ status_data_sets(issue: issue, label: issue_label, today: today),
70
+ blocked_data_sets(
71
+ issue: issue,
72
+ issue_label: issue_label,
73
+ stack: 'blocked',
74
+ issue_start_time: issue_start_time
75
+ ),
76
+ data_set_by_block(
77
+ issue: issue,
78
+ issue_label: issue_label,
79
+ title_label: 'Expedited',
80
+ stack: 'expedited',
81
+ color: CssVariable['--expedited-color'],
82
+ start_date: issue_start_date
83
+ ) { |day| issue.expedited_on_date?(day) }
84
+ ]
85
+ end
86
+
87
+ def sort_by_age! issues:, today:
88
+ issues.sort! do |a, b|
89
+ a.board.cycletime.age(b, today: today) <=> b.board.cycletime.age(a, today: today)
90
+ end
91
+ end
92
+
93
+ def select_aging_issues issues:
94
+ issues.select do |issue|
95
+ cycletime = issue.board.cycletime
96
+ cycletime.started_time(issue) && cycletime.stopped_time(issue).nil?
97
+ end
98
+ end
99
+
100
+ def grow_chart_height_if_too_many_issues aging_issue_count:
85
101
  px_per_bar = 8
86
102
  bars_per_issue = 3
87
103
  preferred_height = aging_issue_count * px_per_bar * bars_per_issue
@@ -220,6 +236,8 @@ class AgingWorkBarChart < ChartBase
220
236
  }
221
237
  end
222
238
 
239
+ return [] if data.empty?
240
+
223
241
  {
224
242
  type: 'bar',
225
243
  data: data,
@@ -8,7 +8,7 @@ class AgingWorkInProgressChart < ChartBase
8
8
  attr_accessor :possible_statuses, :board_id
9
9
  attr_reader :board_columns
10
10
 
11
- def initialize block = nil
11
+ def initialize block
12
12
  super()
13
13
  header_text 'Aging Work in Progress'
14
14
  description_text <<-HTML
@@ -4,6 +4,7 @@ require 'jirametrics/chart_base'
4
4
 
5
5
  class AgingWorkTable < ChartBase
6
6
  attr_accessor :today, :board_id
7
+ attr_reader :any_scrum_boards
7
8
 
8
9
  def initialize block
9
10
  super()
@@ -23,20 +24,21 @@ class AgingWorkTable < ChartBase
23
24
  </p>
24
25
  TEXT
25
26
 
26
- instance_eval(&block) if block
27
+ instance_eval(&block)
27
28
  end
28
29
 
29
30
  def run
30
31
  @today = date_range.end
31
- aging_issues = select_aging_issues
32
+ aging_issues = select_aging_issues + expedited_but_not_started
32
33
 
33
- expedited_but_not_started = @issues.select do |issue|
34
+ wrap_and_render(binding, __FILE__)
35
+ end
36
+
37
+ def expedited_but_not_started
38
+ @issues.select do |issue|
34
39
  cycletime = issue.board.cycletime
35
40
  cycletime.started_time(issue).nil? && cycletime.stopped_time(issue).nil? && issue.expedited?
36
- end
37
- aging_issues += expedited_but_not_started.sort_by(&:created)
38
-
39
- wrap_and_render(binding, __FILE__)
41
+ end.sort_by(&:created)
40
42
  end
41
43
 
42
44
  def select_aging_issues
@@ -54,10 +56,6 @@ class AgingWorkTable < ChartBase
54
56
  aging_issues.sort { |a, b| b.board.cycletime.age(b, today: @today) <=> a.board.cycletime.age(a, today: @today) }
55
57
  end
56
58
 
57
- def icon_span title:, icon:
58
- "<span title='#{title}' style='font-size: 0.8em;'>#{icon}</span>"
59
- end
60
-
61
59
  def expedited_text issue
62
60
  return unless issue.expedited?
63
61
 
@@ -85,13 +83,6 @@ class AgingWorkTable < ChartBase
85
83
  end
86
84
  end
87
85
 
88
- def unmapped_status_text issue
89
- icon_span(
90
- title: "Not visible: The status #{issue.status.name.inspect} is not mapped to any column and will not be visible",
91
- icon: ' 👀'
92
- )
93
- end
94
-
95
86
  def fix_versions_text issue
96
87
  issue.fix_versions.collect do |fix|
97
88
  if fix.released?
@@ -124,19 +115,11 @@ class AgingWorkTable < ChartBase
124
115
  end.join('<br />')
125
116
  end
126
117
 
127
- def current_status_visible? issue
128
- issue.board.visible_columns.any? { |column| column.status_ids.include? issue.status.id }
129
- end
130
-
131
118
  def age_cutoff age = nil
132
119
  @age_cutoff = age.to_i if age
133
120
  @age_cutoff
134
121
  end
135
122
 
136
- def any_scrum_boards?
137
- @any_scrum_boards
138
- end
139
-
140
123
  def parent_hierarchy issue
141
124
  result = []
142
125
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class BoardConfig
4
- attr_reader :id, :project_config
4
+ attr_reader :id, :project_config, :board
5
5
 
6
6
  def initialize id:, block:, project_config:
7
7
  @id = id
@@ -26,6 +26,6 @@ class BoardConfig
26
26
  end
27
27
 
28
28
  def expedited_priority_names *priority_names
29
- @board.expedited_priority_names = priority_names unless priority_names.empty?
29
+ @board.expedited_priority_names = priority_names
30
30
  end
31
31
  end
@@ -2,9 +2,9 @@
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
5
+ :time_range, :data_quality, :holiday_dates, :settings, :issues, :file_system
6
6
  attr_writer :aggregated_project
7
- attr_reader :issues, :canvas_width, :canvas_height
7
+ attr_reader :canvas_width, :canvas_height
8
8
 
9
9
  @@chart_counter = 0
10
10
 
@@ -34,7 +34,7 @@ class ChartBase
34
34
  caller_binding.eval "chart_id='chart#{next_id}'" # chart_id=chart3
35
35
 
36
36
  @html_directory = "#{pathname.dirname}/html"
37
- erb = ERB.new File.read "#{@html_directory}/#{$1}.erb"
37
+ erb = ERB.new file_system.load "#{@html_directory}/#{$1}.erb"
38
38
  erb.result(caller_binding)
39
39
  end
40
40
 
@@ -100,7 +100,7 @@ class ChartBase
100
100
  issues_id = next_id
101
101
 
102
102
  issue_descriptions.sort! { |a, b| a[0].key_as_i <=> b[0].key_as_i }
103
- erb = ERB.new File.read "#{@html_directory}/collapsible_issues_panel.erb"
103
+ erb = ERB.new file_system.load "#{@html_directory}/collapsible_issues_panel.erb"
104
104
  erb.result(binding)
105
105
  end
106
106
 
@@ -153,17 +153,6 @@ class ChartBase
153
153
  end
154
154
  end
155
155
 
156
- def sprints_in_time_range board
157
- board.sprints.select do |sprint|
158
- sprint_end_time = sprint.completed_time || sprint.end_time
159
- sprint_start_time = sprint.start_time
160
- next false if sprint_start_time.nil?
161
-
162
- time_range.include?(sprint_start_time) || time_range.include?(sprint_end_time) ||
163
- (sprint_start_time < time_range.begin && sprint_end_time > time_range.end)
164
- end || []
165
- end
166
-
167
156
  def chart_format object
168
157
  if object.is_a? Time
169
158
  # "2022-04-09T11:38:30-07:00"
@@ -173,12 +162,14 @@ class ChartBase
173
162
  end
174
163
  end
175
164
 
176
- def header_text text
177
- @header_text = text
165
+ def header_text text = nil
166
+ @header_text = text if text
167
+ @header_text
178
168
  end
179
169
 
180
- def description_text text
181
- @description_text = text
170
+ def description_text text = nil
171
+ @description_text = text if text
172
+ @description_text
182
173
  end
183
174
 
184
175
  def format_integer number
@@ -188,26 +179,35 @@ class ChartBase
188
179
  def format_status name_or_id, board:, is_category: false
189
180
  begin
190
181
  statuses = board.possible_statuses.expand_statuses([name_or_id])
191
- rescue RuntimeError => e
192
- return "<span style='color: red'>#{name_or_id}</span>" if e.message.match?(/^Status not found:/)
193
-
194
- throw e
182
+ rescue StatusNotFoundError => e
183
+ return "<span style='color: red'>#{name_or_id}</span>"
195
184
  end
196
- raise "Expected exactly one match and got #{statuses.inspect} for #{name_or_id.inspect}" if statuses.size > 1
197
185
 
198
186
  status = statuses.first
199
187
  color = status_category_color status
200
188
 
189
+ visibility = ''
190
+ if is_category == false && board.visible_columns.none? { |column| column.status_ids.include? status.id }
191
+ visibility = icon_span(
192
+ title: "Not visible: The status #{status.name.inspect} is not mapped to any column and will not be visible",
193
+ icon: ' 👀'
194
+ )
195
+
196
+ end
201
197
  text = is_category ? status.category_name : status.name
202
- "<span title='Category: #{status.category_name}'>#{color_block color.name} #{text}</span>"
198
+ "<span title='Category: #{status.category_name}'>#{color_block color.name} #{text}</span>#{visibility}"
199
+ end
200
+
201
+ def icon_span title:, icon:
202
+ "<span title='#{title}' style='font-size: 0.8em;'>#{icon}</span>"
203
203
  end
204
204
 
205
205
  def status_category_color status
206
206
  case status.category_name
207
- when nil then 'black'
208
207
  when 'To Do' then CssVariable['--status-category-todo-color']
209
208
  when 'In Progress' then CssVariable['--status-category-inprogress-color']
210
209
  when 'Done' then CssVariable['--status-category-done-color']
210
+ else 'black' # Theoretically impossible but seen in prod.
211
211
  end
212
212
  end
213
213
 
@@ -225,18 +225,6 @@ class ChartBase
225
225
  @canvas_responsive
226
226
  end
227
227
 
228
- def filter_issues &block
229
- @filter_issues_block = block
230
- end
231
-
232
- def issues= issues
233
- @issues = issues
234
- return unless @filter_issues_block
235
-
236
- @issues = issues.filter_map { |i| @filter_issues_block.call(i) }.uniq
237
- puts @issues.collect(&:key).join(', ')
238
- end
239
-
240
228
  def color_block color, title: nil
241
229
  result = +''
242
230
  result << "<div class='color_block' style='background: var(#{color});'"
@@ -252,5 +240,5 @@ class ChartBase
252
240
  and any other holidays mentioned in the configuration.
253
241
  </div>
254
242
  TEXT
255
- end
243
+ end
256
244
  end
@@ -6,7 +6,7 @@ class CycletimeHistogram < ChartBase
6
6
  include GroupableIssueChart
7
7
  attr_accessor :possible_statuses
8
8
 
9
- def initialize block = nil
9
+ def initialize block
10
10
  super()
11
11
 
12
12
  header_text 'Cycletime Histogram'
@@ -7,7 +7,7 @@ class CycletimeScatterplot < ChartBase
7
7
 
8
8
  attr_accessor :possible_statuses
9
9
 
10
- def initialize block = nil
10
+ def initialize block
11
11
  super()
12
12
 
13
13
  header_text 'Cycletime Scatterplot'
@@ -3,7 +3,7 @@
3
3
  require 'jirametrics/daily_wip_chart'
4
4
 
5
5
  class DailyWipByAgeChart < DailyWipChart
6
- def initialize block = nil
6
+ def initialize block
7
7
  super(block)
8
8
 
9
9
  add_trend_line line_color: '--aging-work-in-progress-by-age-trend-line-color', group_labels: [
@@ -14,7 +14,7 @@ end
14
14
  class DailyWipChart < ChartBase
15
15
  attr_accessor :possible_statuses
16
16
 
17
- def initialize block = nil
17
+ def initialize block
18
18
  super()
19
19
 
20
20
  header_text default_header_text
@@ -27,9 +27,6 @@ class DailyWipChart < ChartBase
27
27
  default_grouping_rules issue: issue, rules: rules
28
28
  end
29
29
  end
30
-
31
- # Because this one will size itself as needed, we start with a smaller default size
32
- # @canvas_height = 80
33
30
  end
34
31
 
35
32
  def run
@@ -45,8 +42,6 @@ class DailyWipChart < ChartBase
45
42
  end + data_sets
46
43
  end
47
44
 
48
- # grow_chart_height_if_too_many_data_sets data_sets.size
49
-
50
45
  wrap_and_render(binding, __FILE__)
51
46
  end
52
47
 
@@ -174,11 +169,4 @@ class DailyWipChart < ChartBase
174
169
  hidden: false
175
170
  }
176
171
  end
177
-
178
- def grow_chart_height_if_too_many_data_sets count
179
- px_per_bar = 8
180
- bars_per_issue = 0.5
181
- preferred_height = count * px_per_bar * bars_per_issue
182
- @canvas_height = preferred_height if @canvas_height.nil? || @canvas_height < preferred_height
183
- end
184
172
  end
@@ -52,7 +52,7 @@ class DependencyChart < ChartBase
52
52
  end
53
53
 
54
54
  def run
55
- instance_eval(&@rules_block) if @rules_block
55
+ instance_eval(&@rules_block)
56
56
 
57
57
  dot_graph = build_dot_graph
58
58
  return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if dot_graph.nil?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class StoryPointAccuracyChart < ChartBase
4
- def initialize configuration_block = nil
3
+ class EstimateAccuracyChart < ChartBase
4
+ def initialize configuration_block
5
5
  super()
6
6
 
7
7
  header_text 'Estimate Accuracy'
@@ -12,7 +12,7 @@ class StoryPointAccuracyChart < ChartBase
12
12
  </div>
13
13
  <div class="p">
14
14
  The #{color_block '--estimate-accuracy-chart-completed-fill-color'} completed dots indicate
15
- cycletimes.
15
+ cycletimes.
16
16
  <% if @has_aging_data %>
17
17
  The #{color_block '--estimate-accuracy-chart-active-fill-color'} aging dots
18
18
  (click on the legend to turn them on) show the current
@@ -27,7 +27,7 @@ class StoryPointAccuracyChart < ChartBase
27
27
  @y_axis_block = ->(issue, start_time) { story_points_at(issue: issue, start_time: start_time)&.to_f }
28
28
  @y_axis_sort_order = nil
29
29
 
30
- instance_eval(&configuration_block) if configuration_block
30
+ instance_eval(&configuration_block)
31
31
  end
32
32
 
33
33
  def run
@@ -39,26 +39,7 @@ class StoryPointAccuracyChart < ChartBase
39
39
  end
40
40
 
41
41
  def scan_issues
42
- aging_hash = {}
43
- completed_hash = {}
44
-
45
- issues.each do |issue|
46
- cycletime = issue.board.cycletime
47
- start_time = cycletime.started_time(issue)
48
- stop_time = cycletime.stopped_time(issue)
49
-
50
- next unless start_time
51
-
52
- hash = stop_time ? completed_hash : aging_hash
53
-
54
- estimate = @y_axis_block.call issue, start_time
55
- cycle_time = ((stop_time&.to_date || date_range.end) - start_time.to_date).to_i + 1
56
-
57
- next if estimate.nil?
58
-
59
- key = [estimate, cycle_time]
60
- (hash[key] ||= []) << issue
61
- end
42
+ completed_hash, aging_hash = split_into_completed_and_aging issues: issues
62
43
 
63
44
  @has_aging_data = !aging_hash.empty?
64
45
 
@@ -93,7 +74,32 @@ class StoryPointAccuracyChart < ChartBase
93
74
  'borderColor' => border_color,
94
75
  'hidden' => starts_hidden
95
76
  }
96
- end.compact
77
+ end
78
+ end
79
+
80
+ def split_into_completed_and_aging issues:
81
+ aging_hash = {}
82
+ completed_hash = {}
83
+
84
+ issues.each do |issue|
85
+ cycletime = issue.board.cycletime
86
+ start_time = cycletime.started_time(issue)
87
+ stop_time = cycletime.stopped_time(issue)
88
+
89
+ next unless start_time
90
+
91
+ hash = stop_time ? completed_hash : aging_hash
92
+
93
+ estimate = @y_axis_block.call issue, start_time
94
+ cycle_time = ((stop_time&.to_date || date_range.end) - start_time.to_date).to_i + 1
95
+
96
+ next if estimate.nil?
97
+
98
+ key = [estimate, cycle_time]
99
+ (hash[key] ||= []) << issue
100
+ end
101
+
102
+ [completed_hash, aging_hash]
97
103
  end
98
104
 
99
105
  def hash_sorter
@@ -86,7 +86,7 @@ class Exporter
86
86
  daily_wip_by_parent_chart
87
87
  expedited_chart
88
88
  sprint_burndown
89
- story_point_accuracy_chart
89
+ estimate_accuracy_chart
90
90
 
91
91
  dependency_chart do
92
92
  link_rules do |link, rules|
@@ -20,7 +20,7 @@ class ExpeditedChart < ChartBase
20
20
  attr_accessor :issues, :cycletime, :possible_statuses, :date_range
21
21
  attr_reader :expedited_label
22
22
 
23
- def initialize
23
+ def initialize block
24
24
  super()
25
25
 
26
26
  header_text 'Expedited work'
@@ -38,6 +38,8 @@ class ExpeditedChart < ChartBase
38
38
  </div>
39
39
  #{describe_non_working_days}
40
40
  HTML
41
+
42
+ instance_eval(&block)
41
43
  end
42
44
 
43
45
  def run
@@ -32,11 +32,12 @@ class Exporter
32
32
 
33
33
  def initialize file_system: FileSystem.new
34
34
  @project_configs = []
35
- @timezone_offset = '+00:00'
36
35
  @target_path = '.'
37
36
  @holiday_dates = []
38
37
  @downloading = false
39
38
  @file_system = file_system
39
+
40
+ timezone_offset '+00:00'
40
41
  end
41
42
 
42
43
  def export name_filter:
@@ -79,7 +80,6 @@ class Exporter
79
80
  end
80
81
 
81
82
  def project name: nil, &block
82
- raise 'target_path was never set!' if @target_path.nil?
83
83
  raise 'jira_config not set' if @jira_config.nil?
84
84
 
85
85
  @project_configs << ProjectConfig.new(