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 +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 +36 -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_bar_chart.erb +1 -10
- data/lib/jirametrics/html/aging_work_table.erb +1 -1
- data/lib/jirametrics/html/cycletime_scatterplot.erb +1 -10
- data/lib/jirametrics/html/daily_wip_chart.erb +1 -10
- data/lib/jirametrics/html/expedited_chart.erb +1 -10
- data/lib/jirametrics/html/hierarchy_table.erb +1 -1
- data/lib/jirametrics/html/sprint_burndown.erb +1 -10
- data/lib/jirametrics/html/throughput_chart.erb +1 -10
- 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: c87abf98bf68d70050c43537ea6cc58e07afdd780d472b8b6f31e40467739300
|
4
|
+
data.tar.gz: cc9933a325414413446cf5e43231432869e8e2c114f8d76c4ec51e1e0bcd6b3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
|
@@ -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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
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
|
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 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 = @
|
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
|
@@ -38,16 +38,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
38
38
|
plugins: {
|
39
39
|
annotation: {
|
40
40
|
annotations: {
|
41
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
}
|