jirametrics 2.2.1 → 2.4pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) 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/blocked_stalled_change.rb +24 -4
  7. data/lib/jirametrics/board_config.rb +2 -2
  8. data/lib/jirametrics/change_item.rb +13 -5
  9. data/lib/jirametrics/chart_base.rb +27 -39
  10. data/lib/jirametrics/columns_config.rb +4 -0
  11. data/lib/jirametrics/cycletime_histogram.rb +1 -1
  12. data/lib/jirametrics/cycletime_scatterplot.rb +1 -1
  13. data/lib/jirametrics/daily_wip_by_age_chart.rb +1 -1
  14. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +3 -16
  15. data/lib/jirametrics/daily_wip_chart.rb +1 -13
  16. data/lib/jirametrics/data_quality_report.rb +4 -1
  17. data/lib/jirametrics/dependency_chart.rb +1 -1
  18. data/lib/jirametrics/{story_point_accuracy_chart.rb → estimate_accuracy_chart.rb} +31 -25
  19. data/lib/jirametrics/examples/standard_project.rb +1 -1
  20. data/lib/jirametrics/expedited_chart.rb +3 -1
  21. data/lib/jirametrics/exporter.rb +3 -3
  22. data/lib/jirametrics/file_config.rb +12 -8
  23. data/lib/jirametrics/file_system.rb +11 -2
  24. data/lib/jirametrics/groupable_issue_chart.rb +2 -4
  25. data/lib/jirametrics/hierarchy_table.rb +4 -4
  26. data/lib/jirametrics/html/aging_work_table.erb +3 -3
  27. data/lib/jirametrics/html/index.erb +1 -0
  28. data/lib/jirametrics/html_report_config.rb +61 -74
  29. data/lib/jirametrics/issue.rb +129 -57
  30. data/lib/jirametrics/project_config.rb +13 -7
  31. data/lib/jirametrics/sprint_burndown.rb +11 -0
  32. data/lib/jirametrics/status_collection.rb +4 -1
  33. data/lib/jirametrics/throughput_chart.rb +1 -1
  34. data/lib/jirametrics.rb +1 -1
  35. metadata +5 -7
  36. data/lib/jirametrics/experimental/generator.rb +0 -210
  37. data/lib/jirametrics/experimental/info.rb +0 -77
  38. /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: 0b7d2b44b1c29a643b039bdb955fc41c666681c41a17ba608670ccdb9898b276
4
+ data.tar.gz: 97db49aaf2c001c685eb9a16ef7257972c8df717467e8638ea57bd619b62cf5e
5
5
  SHA512:
6
- metadata.gz: 6e07a75c020a54dc077d25b2db1742fc143a4174d5b453813d0d2f3969984f1fc8eb6af18cfa092ecf8ef9d07a4754f9b6013e39c170ee2c3b34122aebf0a387
7
- data.tar.gz: abddaf6fa818847dc6427df90c919c6ade29cf79735c0a2fd64a31b7815d730e146a695fa0696b8896c60f66828dc16c207852a8c116db7fdabc21c90e429964
6
+ metadata.gz: 704a16388b223c738d82f9f13341f23f6b40bb659506b3b6a843e05f8da7c74e1022e7a17d8077cf123bc35a28d14b53ecc5bb7c51d8978705ebea7034b78df3
7
+ data.tar.gz: 3be64f47f590cfe0011d9b9de8afc17ccf357a42a9377bec0d0729d789dcf352ffc1ed3858827268e80a85a72803a1f142702f9a73bb7dfc918941ae637a4125
@@ -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
 
@@ -15,12 +15,12 @@ class BlockedStalledChange
15
15
  @time = time
16
16
  end
17
17
 
18
- def blocked? = @flag || blocked_by_status? || @blocking_issue_keys
19
- def stalled? = @stalled_days || stalled_by_status?
18
+ def blocked? = !!(@flag || blocked_by_status? || @blocking_issue_keys)
19
+ def stalled? = !!(@stalled_days || stalled_by_status?)
20
20
  def active? = !blocked? && !stalled?
21
21
 
22
- def blocked_by_status? = @status && @status_is_blocking
23
- def stalled_by_status? = @status && !@status_is_blocking
22
+ def blocked_by_status? = !!(@status && @status_is_blocking)
23
+ def stalled_by_status? = !!(@status && !@status_is_blocking)
24
24
 
25
25
  def reasons
26
26
  result = []
@@ -35,4 +35,24 @@ class BlockedStalledChange
35
35
  end
36
36
  result.join(', ')
37
37
  end
38
+
39
+ def as_symbol
40
+ if blocked?
41
+ :blocked
42
+ elsif stalled?
43
+ :stalled
44
+ else
45
+ :active
46
+ end
47
+ end
48
+
49
+ def inspect
50
+ text = +"BlockedStalledChange(time: '#{@time}', "
51
+ if active?
52
+ text << 'Active'
53
+ else
54
+ text << reasons
55
+ end
56
+ text << ')'
57
+ end
38
58
  end
@@ -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
@@ -5,7 +5,6 @@ class ChangeItem
5
5
  attr_accessor :value, :old_value, :time
6
6
 
7
7
  def initialize raw:, time:, author:, artificial: false
8
- # raw will only ever be nil in a test and in that case field and value should be passed in
9
8
  @raw = raw
10
9
  @time = time
11
10
  raise "Time must be an object of type Time in the correct timezone: #{@time}" if @time.is_a? String
@@ -36,16 +35,17 @@ class ChangeItem
36
35
  def link? = (field == 'Link')
37
36
 
38
37
  def to_s
39
- message = "ChangeItem(field: #{field.inspect}, value: #{value.inspect}, time: \"#{@time}\""
40
- message += ', artificial' if artificial?
41
- message += ')'
38
+ message = +''
39
+ message << "ChangeItem(field: #{field.inspect}, value: #{value.inspect}, time: #{time_to_s(@time).inspect}"
40
+ message << ', artificial' if artificial?
41
+ message << ')'
42
42
  message
43
43
  end
44
44
 
45
45
  def inspect = to_s
46
46
 
47
47
  def == other
48
- field.eql?(other.field) && value.eql?(other.value) && time.to_s.eql?(other.time.to_s)
48
+ field.eql?(other.field) && value.eql?(other.value) && time_to_s(time).eql?(time_to_s(other.time))
49
49
  end
50
50
 
51
51
  def current_status_matches *status_names_or_ids
@@ -77,4 +77,12 @@ class ChangeItem
77
77
  end
78
78
  end
79
79
  end
80
+
81
+ private
82
+
83
+ def time_to_s time
84
+ # MRI and JRuby return different strings for to_s() so we have to explicitly provide a full
85
+ # format so that tests work under both environments.
86
+ time.strftime '%Y-%m-%d %H:%M:%S %z'
87
+ end
80
88
  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
@@ -34,6 +34,10 @@ class ColumnsConfig
34
34
  @columns << [:string, label, proc]
35
35
  end
36
36
 
37
+ def integer label, proc
38
+ @columns << [:integer, label, proc]
39
+ end
40
+
37
41
  def column_entry_times board_id: nil
38
42
  @file_config.project_config.find_board_by_id(board_id).visible_columns.each do |column|
39
43
  date column.name, first_time_in_status(*column.status_ids)
@@ -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: [
@@ -38,25 +38,12 @@ class DailyWipByBlockedStalledChart < DailyWipChart
38
38
  HTML
39
39
  end
40
40
 
41
- def key_blocked_stalled_change issue:, date:, end_time:
42
- stalled_change = nil
43
- blocked_change = nil
44
-
45
- issue.blocked_stalled_changes_on_date(date: date, end_time: end_time) do |change|
46
- blocked_change = change if change.blocked?
47
- stalled_change = change if change.stalled?
48
- end
49
-
50
- return blocked_change if blocked_change
51
- return stalled_change if stalled_change
52
-
53
- nil
54
- end
55
-
56
41
  def default_grouping_rules issue:, rules:
57
42
  started = issue.board.cycletime.started_time(issue)
58
43
  stopped_date = issue.board.cycletime.stopped_time(issue)&.to_date
59
- change = key_blocked_stalled_change issue: issue, date: rules.current_date, end_time: time_range.end
44
+
45
+ date = rules.current_date
46
+ change = issue.blocked_stalled_by_date(date_range: date..date, chart_end_time: time_range.end)[date]
60
47
 
61
48
  stopped_today = stopped_date == rules.current_date
62
49
 
@@ -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
@@ -71,7 +71,10 @@ class DataQualityReport < ChartBase
71
71
 
72
72
  # Return a format that's easier to assert against
73
73
  def testable_entries
74
- @entries.collect { |entry| [entry.started.to_s, entry.stopped.to_s, entry.issue] }
74
+ format = '%Y-%m-%d %H:%M:%S %z'
75
+ @entries.collect do |entry|
76
+ [entry.started&.strftime(format) || '', entry.stopped&.strftime(format) || '', entry.issue]
77
+ end
75
78
  end
76
79
 
77
80
  def entries_with_problems
@@ -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?