jirametrics 2.8 → 2.9.1pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5bec8084c47e2383d25676b969891c6fc8e427d06427a1caf195378a830ff4f
4
- data.tar.gz: e9364c2b43f66d90a651f50e115751c144f8dfc6f7d247b47dfd17495bdf167e
3
+ metadata.gz: c87abf98bf68d70050c43537ea6cc58e07afdd780d472b8b6f31e40467739300
4
+ data.tar.gz: cc9933a325414413446cf5e43231432869e8e2c114f8d76c4ec51e1e0bcd6b3b
5
5
  SHA512:
6
- metadata.gz: 82c740fa8d23565eb33edf1e244cde1b8b8ef2d88f753091fd0f3cfb7e20aa67c32fc22dca70d4076c25ad40898a0872c13f8af14173a26b33054faf8838df14
7
- data.tar.gz: 66d5db7495165aa4ac0ea36acd27e1b77d2d4b4d4ebb7912fbbffee57c9c160b8cee3fc919884564370db51618438d24fc6fde5e47e2343560aca52b53917d4b
6
+ metadata.gz: ea7f38ef5c9ce0fc3102aec0a6b03b37c5b49fb4518cc800d71520f991bc06ee5e62981e130529d8b9ab4e0737c40930474313499fcf33de9acc52584b42769f
7
+ data.tar.gz: 85050e6890af3a3f49d710da97330b54e5b0b719b09871be938bd74ced7d95bc5cfaa5dc9ee7bcef31dae48e767a2ff5c4b36ca8500c1ae14d8d687a70500407
@@ -3,8 +3,6 @@
3
3
  require 'jirametrics/chart_base'
4
4
 
5
5
  class AgingWorkBarChart < ChartBase
6
- @@next_id = 0
7
-
8
6
  def initialize block
9
7
  super()
10
8
 
@@ -116,7 +114,7 @@ class AgingWorkBarChart < ChartBase
116
114
  issue.changes.each do |change|
117
115
  next unless change.status?
118
116
 
119
- status = issue.find_status_by_id change.value_id, name: change.value
117
+ status = issue.find_or_create_status id: change.value_id, name: change.value
120
118
 
121
119
  unless previous_start.nil? || previous_start < issue_started_time
122
120
  hash = {
@@ -162,8 +160,12 @@ class AgingWorkBarChart < ChartBase
162
160
  end
163
161
 
164
162
  def one_block_change_data_set starting_change:, ending_time:, issue_label:, stack:, issue_start_time:
165
- deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['blocked_color']
166
- deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['stalled_color']
163
+ if settings['blocked_color']
164
+ file_system.deprecated message: 'blocked color should be set via css now', date: '2024-05-03'
165
+ end
166
+ if settings['stalled_color']
167
+ file_system.deprecated message: 'stalled color should be set via css now', date: '2024-05-03'
168
+ end
167
169
 
168
170
  color = settings['blocked_color'] || '--blocked-color'
169
171
  color = settings['stalled_color'] || '--stalled-color' if starting_change.stalled?
@@ -27,9 +27,8 @@ class Board
27
27
  def backlog_statuses
28
28
  if @backlog_statuses.empty? && kanban?
29
29
  status_ids = status_ids_from_column raw['columnConfig']['columns'].first
30
- @backlog_statuses = @possible_statuses.expand_statuses(status_ids) do |unknown_status|
31
- # If a status is returned here that is no longer in the system then there's nothing useful
32
- # we can do about it. Ignore it.
30
+ @backlog_statuses = status_ids.filter_map do |id|
31
+ @possible_statuses.find_by_id id
33
32
  end
34
33
  end
35
34
  @backlog_statuses
@@ -21,11 +21,15 @@ class BoardConfig
21
21
  'If so, remove it from there.'
22
22
  end
23
23
 
24
- @board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block)
24
+ @board.cycletime = CycleTimeConfig.new(
25
+ parent_config: self, label: label, block: block, file_system: project_config.file_system
26
+ )
25
27
  end
26
28
 
27
29
  def expedited_priority_names *priority_names
28
- deprecated date: '2024-09-15', message: 'Expedited priority names are now specified in settings'
30
+ project_config.exporter.file_system.deprecated(
31
+ date: '2024-09-15', message: 'Expedited priority names are now specified in settings'
32
+ )
29
33
  @project_config.settings['expedited_priority_names'] = priority_names
30
34
  end
31
35
  end
@@ -27,9 +27,6 @@ class ChartBase
27
27
 
28
28
  def html_directory
29
29
  pathname = Pathname.new(File.realpath(__FILE__))
30
- # basename = pathname.basename.to_s
31
- # raise "Unexpected filename #{basename.inspect}" unless basename.match?(/^(.+)\.rb$/)
32
-
33
30
  "#{pathname.dirname}/html"
34
31
  end
35
32
 
@@ -133,6 +130,21 @@ class ChartBase
133
130
  result
134
131
  end
135
132
 
133
+ def working_days_annotation
134
+ holidays.each_with_index.collect do |range, index|
135
+ <<~TEXT
136
+ holiday#{index}: {
137
+ drawTime: 'beforeDraw',
138
+ type: 'box',
139
+ xMin: '#{range.begin}T00:00:00',
140
+ xMax: '#{range.end}T23:59:59',
141
+ backgroundColor: #{CssVariable.new('--non-working-days-color').to_json},
142
+ borderColor: #{CssVariable.new('--non-working-days-color').to_json}
143
+ },
144
+ TEXT
145
+ end.join
146
+ end
147
+
136
148
  # Return only the board columns for the current board.
137
149
  def current_board
138
150
  if @board_id.nil?
@@ -179,18 +191,33 @@ class ChartBase
179
191
  @description_text
180
192
  end
181
193
 
194
+ # Convert a number like 1234567 into the string "1,234,567"
182
195
  def format_integer number
183
196
  number.to_s.reverse.scan(/.{1,3}/).join(',').reverse
184
197
  end
185
198
 
186
- def format_status name_or_id, board:, is_category: false
187
- begin
188
- statuses = board.possible_statuses.expand_statuses([name_or_id])
189
- rescue StatusNotFoundError
190
- return "<span style='color: red'>#{name_or_id}</span>"
199
+ # object will be either a Status or a ChangeItem
200
+ # if it's a ChangeItem then use_old_status will specify whether we're using the new or old
201
+ # Either way, is_category will format the category rather than the status
202
+ def format_status object, board:, is_category: false, use_old_status: false
203
+ status = nil
204
+ error_message = nil
205
+
206
+ case object
207
+ when ChangeItem
208
+ id = use_old_status ? object.old_value_id : object.value_id
209
+ status = board.possible_statuses.find_by_id(id)
210
+ if status.nil?
211
+ error_message = use_old_status ? object.old_value : object.value
212
+ end
213
+ when Status
214
+ status = object
215
+ else
216
+ raise "Unexpected type: #{object.class}"
191
217
  end
192
218
 
193
- status = statuses.first
219
+ return "<span style='color: red'>#{error_message}</span>" if error_message
220
+
194
221
  color = status_category_color status
195
222
 
196
223
  visibility = ''
@@ -8,10 +8,14 @@ class CycleTimeConfig
8
8
 
9
9
  attr_reader :label, :parent_config
10
10
 
11
- def initialize parent_config:, label:, block:, today: Date.today
11
+ def initialize parent_config:, label:, block:, file_system: nil, today: Date.today
12
12
  @parent_config = parent_config
13
13
  @label = label
14
14
  @today = today
15
+
16
+ # If we hit something deprecated and this is nil then we'll blow up. Although it's ugly, this
17
+ # may make it easier to find problems in the test code ;-)
18
+ @file_system = file_system
15
19
  instance_eval(&block) unless block.nil?
16
20
  end
17
21
 
@@ -35,17 +39,19 @@ class CycleTimeConfig
35
39
  end
36
40
 
37
41
  def started_time issue
38
- deprecated date: '2024-10-16', message: 'Use started_stopped_times() instead'
42
+ @file_system.deprecated date: '2024-10-16', message: 'Use started_stopped_times() instead'
39
43
  started_stopped_times(issue).first
40
44
  end
41
45
 
42
46
  def stopped_time issue
43
- deprecated date: '2024-10-16', message: 'Use started_stopped_times() instead'
47
+ @file_system.deprecated date: '2024-10-16', message: 'Use started_stopped_times() instead'
44
48
  started_stopped_times(issue).last
45
49
  end
46
50
 
47
51
  def fabricate_change_item time
48
- deprecated date: '2024-12-16', message: 'This method should now return a ChangeItem not a Time', depth: 4
52
+ @file_system.deprecated(
53
+ date: '2024-12-16', message: "This method should now return a ChangeItem not a #{time.class}", depth: 4
54
+ )
49
55
  raw = {
50
56
  'field' => 'Fabricated change',
51
57
  'to' => '0',
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DataQualityReport < ChartBase
4
- attr_reader :original_issue_times # For testing purposes only
4
+ attr_reader :discarded_changes_data, :entries # Both for testing purposes only
5
5
  attr_accessor :board_id
6
6
 
7
7
  class Entry
@@ -19,10 +19,10 @@ class DataQualityReport < ChartBase
19
19
  end
20
20
  end
21
21
 
22
- def initialize original_issue_times
22
+ def initialize discarded_changes_data
23
23
  super()
24
24
 
25
- @original_issue_times = original_issue_times
25
+ @discarded_changes_data = discarded_changes_data
26
26
 
27
27
  header_text 'Data Quality Report'
28
28
  description_text <<-HTML
@@ -50,6 +50,7 @@ class DataQualityReport < ChartBase
50
50
  scan_for_issues_not_started_with_subtasks_that_have entry: entry
51
51
  scan_for_incomplete_subtasks_when_issue_done entry: entry
52
52
  scan_for_discarded_data entry: entry
53
+ scan_for_items_blocked_on_closed_tickets entry: entry
53
54
  end
54
55
 
55
56
  scan_for_issues_on_multiple_boards entries: @entries
@@ -73,6 +74,7 @@ class DataQualityReport < ChartBase
73
74
  result << render_problem_type(:issue_not_started_but_subtasks_have)
74
75
  result << render_problem_type(:incomplete_subtasks_when_issue_done)
75
76
  result << render_problem_type(:issue_on_multiple_boards)
77
+ result << render_problem_type(:items_blocked_on_closed_tickets)
76
78
  result << '</ul>'
77
79
 
78
80
  result
@@ -102,9 +104,13 @@ class DataQualityReport < ChartBase
102
104
 
103
105
  # Return a format that's easier to assert against
104
106
  def testable_entries
105
- format = '%Y-%m-%d %H:%M:%S %z'
107
+ formatter = ->(time) { time&.strftime('%Y-%m-%d %H:%M:%S %z') || '' }
106
108
  @entries.collect do |entry|
107
- [entry.started&.strftime(format) || '', entry.stopped&.strftime(format) || '', entry.issue]
109
+ [
110
+ formatter.call(entry.started),
111
+ formatter.call(entry.stopped),
112
+ entry.issue
113
+ ]
108
114
  end
109
115
  end
110
116
 
@@ -112,10 +118,6 @@ class DataQualityReport < ChartBase
112
118
  @entries.reject { |entry| entry.problems.empty? }
113
119
  end
114
120
 
115
- def category_name_for status_id:, board:
116
- board.possible_statuses.find_by_id(status_id)&.category&.name
117
- end
118
-
119
121
  def initialize_entries
120
122
  @entries = @issues.filter_map do |issue|
121
123
  started, stopped = issue.board.cycletime.started_stopped_times(issue)
@@ -139,10 +141,8 @@ class DataQualityReport < ChartBase
139
141
  def scan_for_completed_issues_without_a_start_time entry:
140
142
  return unless entry.stopped && entry.started.nil?
141
143
 
142
- status_names = entry.issue.changes.filter_map do |change|
143
- next unless change.status?
144
-
145
- format_status change.value, board: entry.issue.board
144
+ status_names = entry.issue.status_changes.filter_map do |change|
145
+ format_status change, board: entry.issue.board
146
146
  end
147
147
 
148
148
  entry.report(
@@ -157,14 +157,14 @@ class DataQualityReport < ChartBase
157
157
  changes_after_done = entry.issue.changes.select do |change|
158
158
  change.status? && change.time >= entry.stopped
159
159
  end
160
- done_status = changes_after_done.shift.value
160
+ done_status = changes_after_done.shift
161
161
 
162
162
  return if changes_after_done.empty?
163
163
 
164
164
  board = entry.issue.board
165
165
  problem = "Completed on #{entry.stopped.to_date} with status #{format_status done_status, board: board}."
166
166
  changes_after_done.each do |change|
167
- problem << " Changed to #{format_status change.value, board: board} on #{change.time.to_date}."
167
+ problem << " Changed to #{format_status change, board: board} on #{change.time.to_date}."
168
168
  end
169
169
  entry.report(
170
170
  problem_key: :status_changes_after_done,
@@ -186,9 +186,9 @@ class DataQualityReport < ChartBase
186
186
  # If it's a backlog status then ignore it. Not supposed to be visible.
187
187
  next if entry.issue.board.backlog_statuses.include?(board.possible_statuses.find_by_id(change.value_id))
188
188
 
189
- detail = "Status #{format_status change.value, board: board} is not on the board"
190
- if issue.board.possible_statuses.expand_statuses(change.value).empty?
191
- detail = "Status #{format_status change.value, board: board} cannot be found at all. Was it deleted?"
189
+ detail = "Status #{format_status change, board: board} is not on the board"
190
+ if issue.board.possible_statuses.find_by_id(change.value_id).nil?
191
+ detail = "Status #{format_status change, board: board} cannot be found at all. Was it deleted?"
192
192
  end
193
193
 
194
194
  # If it's been moved back to backlog then it's on a different report. Ignore it here.
@@ -198,24 +198,24 @@ class DataQualityReport < ChartBase
198
198
  elsif change.old_value.nil?
199
199
  # Do nothing
200
200
  elsif index < last_index
201
- new_category = category_name_for(status_id: change.value_id, board: board)
202
- old_category = category_name_for(status_id: change.old_value_id, board: board)
201
+ new_category = board.possible_statuses.find_by_id(change.value_id).category.name
202
+ old_category = board.possible_statuses.find_by_id(change.old_value_id).category.name
203
203
 
204
204
  if new_category == old_category
205
205
  entry.report(
206
206
  problem_key: :backwords_through_statuses,
207
- detail: "Moved from #{format_status change.old_value, board: board}" \
208
- " to #{format_status change.value, board: board}" \
207
+ detail: "Moved from #{format_status change, use_old_status: true, board: board}" \
208
+ " to #{format_status change, board: board}" \
209
209
  " on #{change.time.to_date}"
210
210
  )
211
211
  else
212
212
  entry.report(
213
213
  problem_key: :backwards_through_status_categories,
214
- detail: "Moved from #{format_status change.old_value, board: board}" \
215
- " to #{format_status change.value, board: board}" \
216
- " on #{change.time.to_date}, " \
217
- " crossing from category #{format_status old_category, board: board, is_category: true}" \
218
- " to #{format_status new_category, board: board, is_category: true}."
214
+ detail: "Moved from #{format_status change, use_old_status: true, board: board}" \
215
+ " to #{format_status change, board: board}" \
216
+ " on #{change.time.to_date}," \
217
+ " crossing from category #{format_status change, use_old_status: true, board: board, is_category: true}" \
218
+ " to #{format_status change, board: board, is_category: true}."
219
219
  )
220
220
  end
221
221
  end
@@ -224,16 +224,14 @@ class DataQualityReport < ChartBase
224
224
  end
225
225
 
226
226
  def scan_for_issues_not_created_in_a_backlog_status entry:, backlog_statuses:
227
- return if backlog_statuses.empty?
228
-
229
227
  creation_change = entry.issue.changes.find { |issue| issue.status? }
230
228
 
231
229
  return if backlog_statuses.any? { |status| status.id == creation_change.value_id }
232
230
 
233
- status_string = backlog_statuses.collect { |s| format_status s.name, board: entry.issue.board }.join(', ')
231
+ status_string = backlog_statuses.collect { |s| format_status s, board: entry.issue.board }.join(', ')
234
232
  entry.report(
235
233
  problem_key: :created_in_wrong_status,
236
- detail: "Created in #{format_status creation_change.value, board: entry.issue.board}, " \
234
+ detail: "Created in #{format_status creation_change, board: entry.issue.board}, " \
237
235
  "which is not one of the backlog statuses for this board: #{status_string}"
238
236
  )
239
237
  end
@@ -266,6 +264,20 @@ class DataQualityReport < ChartBase
266
264
  )
267
265
  end
268
266
 
267
+ def scan_for_items_blocked_on_closed_tickets entry:
268
+ entry.issue.issue_links.each do |link|
269
+ this_active = !entry.stopped
270
+ other_active = !link.other_issue.board.cycletime.started_stopped_times(link.other_issue).last
271
+ next unless this_active && !other_active
272
+
273
+ entry.report(
274
+ problem_key: :items_blocked_on_closed_tickets,
275
+ detail: "#{entry.issue.key} thinks it's blocked by #{link.other_issue.key}, " \
276
+ "except #{link.other_issue.key} is closed."
277
+ )
278
+ end
279
+ end
280
+
269
281
  def subtask_label subtask
270
282
  "<img src='#{subtask.type_icon_url}' /> #{link_to_issue(subtask)} #{subtask.summary[..50].inspect}"
271
283
  end
@@ -314,10 +326,10 @@ class DataQualityReport < ChartBase
314
326
  end
315
327
 
316
328
  def scan_for_discarded_data entry:
317
- hash = @original_issue_times[entry.issue]
329
+ hash = @discarded_changes_data&.find { |a| a[:issue] == entry.issue }
318
330
  return if hash.nil?
319
331
 
320
- old_start_time = hash[:started_time]
332
+ old_start_time = hash[:original_start_time]
321
333
  cutoff_time = hash[:cutoff_time]
322
334
 
323
335
  old_start_date = old_start_time.to_date
@@ -352,7 +364,7 @@ class DataQualityReport < ChartBase
352
364
  <<-HTML
353
365
  #{label_issues problems.size} have had information discarded. This configuration is set
354
366
  to "reset the clock" if an item is moved back to the backlog after it's been started. This hides important
355
- information and makes the data less accurate. <b>Moving items back to the backlog is strongly discouraged.</b> HTML
367
+ information and makes the data less accurate. <b>Moving items back to the backlog is strongly discouraged.</b>
356
368
  HTML
357
369
  end
358
370
 
@@ -437,4 +449,11 @@ class DataQualityReport < ChartBase
437
449
  could result in more data points showing up on a chart then there really should be.
438
450
  HTML
439
451
  end
452
+
453
+ def render_items_blocked_on_closed_tickets problems
454
+ <<-HTML
455
+ For #{label_issues problems.size}, the issue is identified as being blocked by another issue. Yet,
456
+ that other issue is already completed so, by definition, it can't still be blocking.
457
+ HTML
458
+ end
440
459
  end
@@ -28,8 +28,8 @@ class Exporter
28
28
  block = boards[board_id]
29
29
  if block == :default
30
30
  block = lambda do |_|
31
- start_at first_time_in_status_category('In Progress')
32
- stop_at still_in_status_category('Done')
31
+ start_at first_time_in_status_category(:indeterminate)
32
+ stop_at still_in_status_category(:done)
33
33
  end
34
34
  end
35
35
  board id: board_id do
@@ -2,18 +2,6 @@
2
2
 
3
3
  require 'fileutils'
4
4
 
5
- class Object
6
- def deprecated message:, date:, depth: 2
7
- text = +''
8
- text << "Deprecated(#{date}): "
9
- text << message
10
- caller(1..depth).each do |line|
11
- text << "\n-> Called from #{line}"
12
- end
13
- warn text
14
- end
15
- end
16
-
17
5
  class Exporter
18
6
  attr_reader :project_configs
19
7
  attr_accessor :file_system
@@ -85,6 +85,11 @@ class FileConfig
85
85
 
86
86
  def html_report &block
87
87
  assert_only_one_filetype_config_set
88
+ if block.nil?
89
+ project_config.file_system.warning 'No charts were specified for the report. This is almost certainly a mistake.'
90
+ block = ->(_) {}
91
+ end
92
+
88
93
  @html_report = HtmlReportConfig.new file_config: self, block: block
89
94
  end
90
95
 
@@ -120,4 +125,11 @@ class FileConfig
120
125
  @file_suffix = suffix unless suffix.nil?
121
126
  @file_suffix
122
127
  end
128
+
129
+ def children
130
+ result = []
131
+ result << @columns if @columns
132
+ result << @html_report if @html_report
133
+ result
134
+ end
123
135
  end
@@ -31,13 +31,18 @@ class FileSystem
31
31
  File.write(filename, content)
32
32
  end
33
33
 
34
- def warning message
35
- log "Warning: #{message}", also_write_to_stderr: true
34
+ def warning message, more: nil
35
+ log "Warning: #{message}", more: more, also_write_to_stderr: true
36
36
  end
37
37
 
38
- def log message, also_write_to_stderr: false
38
+ def log message, more: nil, also_write_to_stderr: false
39
+ message += " See #{logfile_name} for more details about this message." if more
40
+
39
41
  logfile.puts message
40
- $stderr.puts message if also_write_to_stderr # rubocop:disable Style/StderrPuts
42
+ logfile.puts more if more
43
+ return unless also_write_to_stderr
44
+
45
+ $stderr.puts message # rubocop:disable Style/StderrPuts
41
46
  end
42
47
 
43
48
  # In some Jira instances, a sizeable portion of the JSON is made up of empty fields. I've seen
@@ -59,4 +64,14 @@ class FileSystem
59
64
  def file_exist? filename
60
65
  File.exist? filename
61
66
  end
67
+
68
+ def deprecated message:, date:, depth: 2
69
+ text = +''
70
+ text << "Deprecated(#{date}): "
71
+ text << message
72
+ caller(1..depth).each do |line|
73
+ text << "\n-> Called from #{line}"
74
+ end
75
+ log text, also_write_to_stderr: true
76
+ end
62
77
  end
@@ -6,9 +6,7 @@ require 'jirametrics/grouping_rules'
6
6
  module GroupableIssueChart
7
7
  def init_configuration_block user_provided_block, &default_block
8
8
  instance_eval(&user_provided_block)
9
- return if @group_by_block
10
-
11
- instance_eval(&default_block)
9
+ instance_eval(&default_block) unless @group_by_block
12
10
  end
13
11
 
14
12
  def grouping_rules &block
@@ -38,16 +38,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
38
38
  plugins: {
39
39
  annotation: {
40
40
  annotations: {
41
- <% holidays.each_with_index do |range, index| %>
42
- holiday<%= index %>: {
43
- drawTime: 'beforeDraw',
44
- type: 'box',
45
- xMin: '<%= range.begin %>T00:00:00',
46
- xMax: '<%= range.end %>T23:59:59',
47
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
48
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
49
- },
50
- <% end %>
41
+ <%= working_days_annotation %>
51
42
 
52
43
  <% if percentage_line_x %>
53
44
  line: {
@@ -40,7 +40,7 @@
40
40
  </div>
41
41
  <% end %>
42
42
  </td>
43
- <td><%= format_status issue.status.name, board: issue.board %></td>
43
+ <td><%= format_status issue.status, board: issue.board %></td>
44
44
  <td><%= fix_versions_text(issue) %></td>
45
45
  <% if any_scrum_boards %>
46
46
  <td><%= sprints_text(issue) %></td>
@@ -53,16 +53,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
53
53
  autocolors: false,
54
54
  annotation: {
55
55
  annotations: {
56
- <% holidays.each_with_index do |range, index| %>
57
- holiday<%= index %>: {
58
- drawTime: 'beforeDraw',
59
- type: 'box',
60
- xMin: '<%= range.begin %>T00:00:00',
61
- xMax: '<%= range.end %>T23:59:59',
62
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
63
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
64
- },
65
- <% end %>
56
+ <%= working_days_annotation %>
66
57
 
67
58
  <% @percentage_lines.each_with_index do |args, index| %>
68
59
  <% percent, color = args %>
@@ -50,16 +50,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
50
50
  },
51
51
  annotation: {
52
52
  annotations: {
53
- <% holidays.each_with_index do |range, index| %>
54
- holiday<%= index %>: {
55
- drawTime: 'beforeDraw',
56
- type: 'box',
57
- xMin: '<%= range.begin %>T00:00:00',
58
- xMax: '<%= range.end %>T23:59:59',
59
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
60
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
61
- },
62
- <% end %>
53
+ <%= working_days_annotation %>
63
54
  }
64
55
  },
65
56
  legend: {
@@ -55,16 +55,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
55
55
  autocolors: false,
56
56
  annotation: {
57
57
  annotations: {
58
- <% holidays.each_with_index do |range, index| %>
59
- holiday<%= index %>: {
60
- drawTime: 'beforeDraw',
61
- type: 'box',
62
- xMin: '<%= range.begin %>T00:00:00',
63
- xMax: '<%= range.end %>T23:59:59',
64
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
65
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
66
- },
67
- <% end %>
58
+ <%= working_days_annotation %>
68
59
  }
69
60
  }
70
61
  }
@@ -22,7 +22,7 @@
22
22
  </span>
23
23
  </td>
24
24
  <td><span style="color: <%= color %>; font-style: italic;"><%= issue.summary[0..80] %></span></td>
25
- <td><%= format_status issue.status.name, board: issue.board %></td>
25
+ <td><%= format_status issue.status, board: issue.board %></td>
26
26
  </tr>
27
27
  <% end %>
28
28
  </tbody>
@@ -56,16 +56,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
56
56
  },
57
57
  annotation: {
58
58
  annotations: {
59
- <% holidays().each_with_index do |range, index| %>
60
- holiday<%= index %>: {
61
- drawTime: 'beforeDraw',
62
- type: 'box',
63
- xMin: '<%= range.begin %>T00:00:00',
64
- xMax: '<%= range.end %>T23:59:59',
65
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
66
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
67
- },
68
- <% end %>
59
+ <%= working_days_annotation %>
69
60
  }
70
61
  }
71
62
  }
@@ -52,16 +52,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
52
52
  },
53
53
  annotation: {
54
54
  annotations: {
55
- <% holidays.each_with_index do |range, index| %>
56
- holiday<%= index %>: {
57
- drawTime: 'beforeDraw',
58
- type: 'box',
59
- xMin: '<%= range.begin %>T00:00:00',
60
- xMax: '<%= range.end %>T23:59:59',
61
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
62
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
63
- },
64
- <% end %>
55
+ <%= working_days_annotation %>
65
56
  }
66
57
  }
67
58
  }