jirametrics 2.8 → 2.9
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 +4 -4
- data/lib/jirametrics/aging_work_bar_chart.rb +7 -5
- data/lib/jirametrics/board.rb +2 -3
- data/lib/jirametrics/board_config.rb +6 -2
- data/lib/jirametrics/chart_base.rb +21 -9
- data/lib/jirametrics/cycletime_config.rb +10 -4
- data/lib/jirametrics/data_quality_report.rb +53 -34
- data/lib/jirametrics/examples/standard_project.rb +2 -2
- data/lib/jirametrics/exporter.rb +0 -12
- data/lib/jirametrics/file_config.rb +12 -0
- data/lib/jirametrics/file_system.rb +19 -4
- data/lib/jirametrics/groupable_issue_chart.rb +1 -3
- data/lib/jirametrics/html/aging_work_table.erb +1 -1
- data/lib/jirametrics/html/hierarchy_table.erb +1 -1
- data/lib/jirametrics/html_report_config.rb +18 -23
- data/lib/jirametrics/issue.rb +51 -27
- data/lib/jirametrics/project_config.rb +102 -45
- data/lib/jirametrics/status.rb +23 -1
- data/lib/jirametrics/status_collection.rb +69 -68
- data/lib/jirametrics/value_equality.rb +2 -2
- data/lib/jirametrics.rb +0 -1
- metadata +4 -9
- data/lib/jirametrics/discard_changes_before.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 892b3686d9d53b05f9dd064524e99d38753b7c79685794a6bd6234d2d538e52d
|
4
|
+
data.tar.gz: c9412dfecec193196d523e1d811e89793269568eb31708192a075440d2806cb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1ccae2dfc047912ced09176cac3af057b66350eb1436fe053457ff299629dafe801acb337c9ffa3702ec7c1ef0d3dd2dad84c8e6d2ff2842f6814d1ef6e43f2
|
7
|
+
data.tar.gz: e5b635a59c4026b51bd84d65cf3230cd90964f82cbb752e04534f1a4d7c59447e174f3d0bf480babe229b4fb91244f89f897d6da2c7c855ab189a4e943cd0a6d
|
@@ -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.
|
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
|
-
|
166
|
-
|
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?
|
data/lib/jirametrics/board.rb
CHANGED
@@ -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 =
|
31
|
-
|
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(
|
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
|
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
|
|
@@ -179,18 +176,33 @@ class ChartBase
|
|
179
176
|
@description_text
|
180
177
|
end
|
181
178
|
|
179
|
+
# Convert a number like 1234567 into the string "1,234,567"
|
182
180
|
def format_integer number
|
183
181
|
number.to_s.reverse.scan(/.{1,3}/).join(',').reverse
|
184
182
|
end
|
185
183
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
184
|
+
# object will be either a Status or a ChangeItem
|
185
|
+
# if it's a ChangeItem then use_old_status will specify whether we're using the new or old
|
186
|
+
# Either way, is_category will format the category rather than the status
|
187
|
+
def format_status object, board:, is_category: false, use_old_status: false
|
188
|
+
status = nil
|
189
|
+
error_message = nil
|
190
|
+
|
191
|
+
case object
|
192
|
+
when ChangeItem
|
193
|
+
id = use_old_status ? object.old_value_id : object.value_id
|
194
|
+
status = board.possible_statuses.find_by_id(id)
|
195
|
+
if status.nil?
|
196
|
+
error_message = use_old_status ? object.old_value : object.value
|
197
|
+
end
|
198
|
+
when Status
|
199
|
+
status = object
|
200
|
+
else
|
201
|
+
raise "Unexpected type: #{object.class}"
|
191
202
|
end
|
192
203
|
|
193
|
-
|
204
|
+
return "<span style='color: red'>#{error_message}</span>" if error_message
|
205
|
+
|
194
206
|
color = status_category_color status
|
195
207
|
|
196
208
|
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
|
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 :
|
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
|
22
|
+
def initialize discarded_changes_data
|
23
23
|
super()
|
24
24
|
|
25
|
-
@
|
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
|
-
|
107
|
+
formatter = ->(time) { time&.strftime('%Y-%m-%d %H:%M:%S %z') || '' }
|
106
108
|
@entries.collect do |entry|
|
107
|
-
[
|
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.
|
143
|
-
|
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
|
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
|
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
|
190
|
-
if issue.board.possible_statuses.
|
191
|
-
detail = "Status #{format_status change
|
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 =
|
202
|
-
old_category =
|
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
|
208
|
-
" to #{format_status change
|
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
|
215
|
-
" to #{format_status change
|
216
|
-
" on #{change.time.to_date},
|
217
|
-
" crossing from category #{format_status
|
218
|
-
" to #{format_status
|
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
|
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
|
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 on #{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 = @
|
329
|
+
hash = @discarded_changes_data&.find { |a| a[:issue] == entry.issue }
|
318
330
|
return if hash.nil?
|
319
331
|
|
320
|
-
old_start_time = hash[:
|
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>
|
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(
|
32
|
-
stop_at still_in_status_category(
|
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
|
data/lib/jirametrics/exporter.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
@@ -40,7 +40,7 @@
|
|
40
40
|
</div>
|
41
41
|
<% end %>
|
42
42
|
</td>
|
43
|
-
<td><%= format_status issue.status
|
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>
|
@@ -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
|
25
|
+
<td><%= format_status issue.status, board: issue.board %></td>
|
26
26
|
</tr>
|
27
27
|
<% end %>
|
28
28
|
</tbody>
|
@@ -5,16 +5,15 @@ require 'jirametrics/self_or_issue_dispatcher'
|
|
5
5
|
|
6
6
|
class HtmlReportConfig
|
7
7
|
include SelfOrIssueDispatcher
|
8
|
-
include DiscardChangesBefore
|
9
8
|
|
10
|
-
attr_reader :file_config, :sections
|
9
|
+
attr_reader :file_config, :sections, :charts
|
11
10
|
|
12
11
|
def self.define_chart name:, classname:, deprecated_warning: nil, deprecated_date: nil
|
13
12
|
lines = []
|
14
13
|
lines << "def #{name} &block"
|
15
14
|
lines << ' block = ->(_) {} unless block'
|
16
15
|
if deprecated_warning
|
17
|
-
lines << " deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
|
16
|
+
lines << " file_system.deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
|
18
17
|
end
|
19
18
|
lines << " execute_chart #{classname}.new(block)"
|
20
19
|
lines << 'end'
|
@@ -43,14 +42,15 @@ class HtmlReportConfig
|
|
43
42
|
def initialize file_config:, block:
|
44
43
|
@file_config = file_config
|
45
44
|
@block = block
|
46
|
-
@sections = []
|
45
|
+
@sections = [] # Where we store the chunks of text that will be assembled into the HTML
|
46
|
+
@charts = [] # Where we store all the charts we executed so we can assert against them.
|
47
47
|
end
|
48
48
|
|
49
49
|
def cycletime label = nil, &block
|
50
50
|
@file_config.project_config.all_boards.each_value do |board|
|
51
51
|
raise 'Multiple cycletimes not supported' if board.cycletime
|
52
52
|
|
53
|
-
board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block)
|
53
|
+
board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block, file_system: file_system)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -64,7 +64,7 @@ class HtmlReportConfig
|
|
64
64
|
|
65
65
|
# The quality report has to be generated last because otherwise cycletime won't have been
|
66
66
|
# set. Then we have to rotate it to the first position so it's at the top of the report.
|
67
|
-
execute_chart DataQualityReport.new(
|
67
|
+
execute_chart DataQualityReport.new(file_config.project_config.discarded_changes_data)
|
68
68
|
@sections.rotate!(-1)
|
69
69
|
|
70
70
|
html create_footer
|
@@ -101,9 +101,8 @@ class HtmlReportConfig
|
|
101
101
|
base_css
|
102
102
|
end
|
103
103
|
|
104
|
-
def board_id id
|
105
|
-
@board_id = id
|
106
|
-
@board_id
|
104
|
+
def board_id id
|
105
|
+
@board_id = id
|
107
106
|
end
|
108
107
|
|
109
108
|
def timezone_offset
|
@@ -143,19 +142,6 @@ class HtmlReportConfig
|
|
143
142
|
end
|
144
143
|
end
|
145
144
|
|
146
|
-
def discard_changes_before_hook issues_cutoff_times
|
147
|
-
# raise 'Cycletime must be defined before using discard_changes_before' unless @cycletime
|
148
|
-
|
149
|
-
@original_issue_times = {}
|
150
|
-
issues_cutoff_times.each do |issue, cutoff_time|
|
151
|
-
started = issue.board.cycletime.started_stopped_times(issue).first
|
152
|
-
if started && started <= cutoff_time
|
153
|
-
# We only need to log this if data was discarded
|
154
|
-
@original_issue_times[issue] = { cutoff_time: cutoff_time, started_time: started }
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
145
|
def dependency_chart &block
|
160
146
|
execute_chart DependencyChart.new block
|
161
147
|
end
|
@@ -175,7 +161,7 @@ class HtmlReportConfig
|
|
175
161
|
chart.settings = settings
|
176
162
|
|
177
163
|
chart.all_boards = project_config.all_boards
|
178
|
-
chart.board_id = find_board_id
|
164
|
+
chart.board_id = find_board_id
|
179
165
|
chart.holiday_dates = project_config.exporter.holiday_dates
|
180
166
|
|
181
167
|
time_range = @file_config.project_config.time_range
|
@@ -184,6 +170,7 @@ class HtmlReportConfig
|
|
184
170
|
|
185
171
|
after_init_block&.call chart
|
186
172
|
|
173
|
+
@charts << chart
|
187
174
|
html chart.run
|
188
175
|
end
|
189
176
|
|
@@ -216,4 +203,12 @@ class HtmlReportConfig
|
|
216
203
|
</section>
|
217
204
|
HTML
|
218
205
|
end
|
206
|
+
|
207
|
+
def discard_changes_before status_becomes: nil, &block
|
208
|
+
file_system.deprecated(
|
209
|
+
date: '2025-01-09',
|
210
|
+
message: 'discard_changes_before is now only supported at the project level'
|
211
|
+
)
|
212
|
+
file_config.project_config.discard_changes_before status_becomes: status_becomes, &block
|
213
|
+
end
|
219
214
|
end
|