jirametrics 2.0 → 2.12.1
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/aggregate_config.rb +19 -26
- data/lib/jirametrics/aging_work_bar_chart.rb +79 -54
- data/lib/jirametrics/aging_work_in_progress_chart.rb +106 -40
- data/lib/jirametrics/aging_work_table.rb +84 -54
- data/lib/jirametrics/anonymizer.rb +6 -5
- data/lib/jirametrics/blocked_stalled_change.rb +24 -4
- data/lib/jirametrics/board.rb +51 -23
- data/lib/jirametrics/board_config.rb +9 -4
- data/lib/jirametrics/board_movement_calculator.rb +155 -0
- data/lib/jirametrics/change_item.rb +56 -21
- data/lib/jirametrics/chart_base.rb +101 -61
- data/lib/jirametrics/columns_config.rb +4 -0
- data/lib/jirametrics/css_variable.rb +33 -0
- data/lib/jirametrics/cycletime_config.rb +59 -8
- data/lib/jirametrics/cycletime_histogram.rb +69 -4
- data/lib/jirametrics/cycletime_scatterplot.rb +11 -15
- data/lib/jirametrics/daily_view.rb +277 -0
- data/lib/jirametrics/daily_wip_by_age_chart.rb +44 -20
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +37 -35
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +38 -0
- data/lib/jirametrics/daily_wip_chart.rb +61 -14
- data/lib/jirametrics/data_quality_report.rb +222 -41
- data/lib/jirametrics/dependency_chart.rb +54 -23
- data/lib/jirametrics/download_config.rb +12 -0
- data/lib/jirametrics/downloader.rb +86 -56
- data/lib/jirametrics/estimate_accuracy_chart.rb +173 -0
- data/lib/jirametrics/estimation_configuration.rb +25 -0
- data/lib/jirametrics/examples/aggregated_project.rb +22 -39
- data/lib/jirametrics/examples/standard_project.rb +26 -48
- data/lib/jirametrics/expedited_chart.rb +28 -25
- data/lib/jirametrics/exporter.rb +59 -32
- data/lib/jirametrics/file_config.rb +35 -14
- data/lib/jirametrics/file_system.rb +48 -3
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
- data/lib/jirametrics/groupable_issue_chart.rb +2 -6
- data/lib/jirametrics/grouping_rules.rb +7 -1
- data/lib/jirametrics/hierarchy_table.rb +4 -4
- data/lib/jirametrics/html/aging_work_bar_chart.erb +13 -16
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +28 -5
- data/lib/jirametrics/html/aging_work_table.erb +21 -25
- data/lib/jirametrics/html/cycletime_histogram.erb +83 -3
- data/lib/jirametrics/html/cycletime_scatterplot.erb +9 -12
- data/lib/jirametrics/html/daily_wip_chart.erb +17 -13
- data/lib/jirametrics/html/{story_point_accuracy_chart.erb → estimate_accuracy_chart.erb} +9 -4
- data/lib/jirametrics/html/expedited_chart.erb +10 -13
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
- data/lib/jirametrics/html/hierarchy_table.erb +2 -2
- data/lib/jirametrics/html/index.css +280 -0
- data/lib/jirametrics/html/index.erb +33 -39
- data/lib/jirametrics/html/sprint_burndown.erb +10 -14
- data/lib/jirametrics/html/throughput_chart.erb +10 -13
- data/lib/jirametrics/html_report_config.rb +110 -86
- data/lib/jirametrics/issue.rb +390 -109
- data/lib/jirametrics/issue_collection.rb +33 -0
- data/lib/jirametrics/jira_gateway.rb +33 -12
- data/lib/jirametrics/project_config.rb +276 -147
- data/lib/jirametrics/rules.rb +2 -2
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/settings.json +11 -0
- data/lib/jirametrics/sprint.rb +1 -0
- data/lib/jirametrics/sprint_burndown.rb +59 -40
- data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
- data/lib/jirametrics/status.rb +84 -19
- data/lib/jirametrics/status_collection.rb +86 -39
- data/lib/jirametrics/throughput_chart.rb +12 -4
- data/lib/jirametrics/user.rb +12 -0
- data/lib/jirametrics/value_equality.rb +2 -2
- data/lib/jirametrics.rb +29 -7
- metadata +20 -17
- data/lib/jirametrics/discard_changes_before.rb +0 -37
- data/lib/jirametrics/experimental/generator.rb +0 -210
- data/lib/jirametrics/experimental/info.rb +0 -77
- data/lib/jirametrics/html/data_quality_report.erb +0 -126
- data/lib/jirametrics/story_point_accuracy_chart.rb +0 -134
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7565c5b6320b9c4375a3feb27fcaf80f96d9c058041b71584f287e6a3d7eb399
|
|
4
|
+
data.tar.gz: 4554c7edbc7c42e01ad4838094ab922819c2e71818a46907f25740e35c327aa6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 021c1f989117b1fbb8708183d9ad8e79b049060989c1f20d31121a486d25f1ae2d1c036e013279479cd3ac6f5b101a48d8c9f4943570107c9cc4b40485ea6a4f
|
|
7
|
+
data.tar.gz: 7f7e334a2161e23ef5ae8a754aa3f12524c1bb130893ca3df3e8574f7a44286ef33c8a03b2ac2790ccb8e8b792ce0fb6c17b1d69d2da068ea48dbce1b4c514a1
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'date'
|
|
4
4
|
|
|
5
5
|
class AggregateConfig
|
|
6
|
-
attr_reader :project_config
|
|
6
|
+
attr_reader :project_config, :included_projects
|
|
7
7
|
|
|
8
8
|
def initialize project_config:, block:
|
|
9
9
|
@project_config = project_config
|
|
@@ -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
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
44
|
+
file_system.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,28 +58,16 @@ class AggregateConfig
|
|
|
57
58
|
else
|
|
58
59
|
issues = project.file_configs.first.issues
|
|
59
60
|
if project.file_configs.size > 1
|
|
60
|
-
|
|
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
|
-
@project_config.add_issues issues
|
|
65
|
-
end
|
|
66
|
-
|
|
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
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
66
|
+
if issues.nil?
|
|
67
|
+
file_system.warning "No issues found for #{project_name}"
|
|
68
|
+
else
|
|
69
|
+
@project_config.add_issues issues
|
|
70
|
+
end
|
|
82
71
|
end
|
|
83
72
|
|
|
84
73
|
def find_time_range projects:
|
|
@@ -92,8 +81,12 @@ class AggregateConfig
|
|
|
92
81
|
latest = range.end if latest.nil? || range.end > latest
|
|
93
82
|
end
|
|
94
83
|
|
|
95
|
-
raise "Can't calculate range" if earliest.nil? || latest.nil?
|
|
96
|
-
|
|
97
84
|
earliest..latest
|
|
98
85
|
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def file_system
|
|
90
|
+
@project_config.exporter.file_system
|
|
91
|
+
end
|
|
99
92
|
end
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
require 'jirametrics/chart_base'
|
|
4
4
|
|
|
5
5
|
class AgingWorkBarChart < ChartBase
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def initialize block = nil
|
|
6
|
+
def initialize block
|
|
9
7
|
super()
|
|
10
8
|
|
|
11
9
|
header_text 'Aging Work Bar Chart'
|
|
@@ -17,69 +15,87 @@ class AgingWorkBarChart < ChartBase
|
|
|
17
15
|
<p>
|
|
18
16
|
There are potentially three bars for each issue, although a bar may be missing if the issue has no
|
|
19
17
|
information relevant to that. Hovering over any of the bars will provide more details.
|
|
20
|
-
<ol
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
<ol>
|
|
19
|
+
<li>The top bar tells you what status the issue is in at any time. The colour indicates the
|
|
20
|
+
status category, which will be one of #{color_block '--status-category-todo-color'} To Do,
|
|
21
|
+
#{color_block '--status-category-inprogress-color'} In Progress,
|
|
22
|
+
or #{color_block '--status-category-done-color'} Done</li>
|
|
23
|
+
<li>The middle bar indicates #{color_block '--blocked-color'} blocked
|
|
24
|
+
or #{color_block '--stalled-color'} stalled.</li>
|
|
25
|
+
<li>The bottom bar indicated #{color_block '--expedited-color'} expedited.</li>
|
|
26
|
+
</ol>
|
|
28
27
|
</p>
|
|
28
|
+
#{describe_non_working_days}
|
|
29
29
|
HTML
|
|
30
30
|
|
|
31
31
|
# Because this one will size itself as needed, we start with a smaller default size
|
|
32
32
|
@canvas_height = 80
|
|
33
33
|
|
|
34
|
-
instance_eval(&block)
|
|
34
|
+
instance_eval(&block)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def run
|
|
38
|
-
aging_issues = @issues
|
|
39
|
-
cycletime = issue.board.cycletime
|
|
40
|
-
cycletime.started_time(issue) && cycletime.stopped_time(issue).nil?
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
grow_chart_height_if_too_many_issues aging_issues.size
|
|
38
|
+
aging_issues = select_aging_issues issues: @issues
|
|
44
39
|
|
|
45
40
|
today = date_range.end
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
|
|
55
|
-
[
|
|
56
|
-
status_data_sets(issue: issue, label: issue_label, today: today),
|
|
57
|
-
blocked_data_sets(
|
|
58
|
-
issue: issue,
|
|
59
|
-
issue_label: issue_label,
|
|
60
|
-
stack: 'blocked',
|
|
61
|
-
issue_start_time: issue_start_time
|
|
62
|
-
),
|
|
63
|
-
data_set_by_block(
|
|
64
|
-
issue: issue,
|
|
65
|
-
issue_label: issue_label,
|
|
66
|
-
title_label: 'Expedited',
|
|
67
|
-
stack: 'expedited',
|
|
68
|
-
color: 'red',
|
|
69
|
-
start_date: issue_start_date
|
|
70
|
-
) { |day| issue.expedited_on_date?(day) }
|
|
71
|
-
].compact.flatten.each do |data|
|
|
72
|
-
data_sets << data
|
|
73
|
-
end
|
|
74
|
-
end
|
|
41
|
+
sort_by_age! issues: aging_issues, today: today
|
|
42
|
+
|
|
43
|
+
grow_chart_height_if_too_many_issues aging_issue_count: aging_issues.size
|
|
44
|
+
|
|
45
|
+
data_sets = aging_issues
|
|
46
|
+
.collect { |issue| data_sets_for_one_issue issue: issue, today: today }
|
|
47
|
+
.flatten
|
|
48
|
+
.compact
|
|
75
49
|
|
|
76
50
|
percentage = calculate_percent_line
|
|
77
51
|
percentage_line_x = date_range.end - calculate_percent_line if percentage
|
|
78
52
|
|
|
53
|
+
if aging_issues.empty?
|
|
54
|
+
@description_text = '<p>There is no aging work</p>'
|
|
55
|
+
return render_top_text(binding)
|
|
56
|
+
end
|
|
57
|
+
|
|
79
58
|
wrap_and_render(binding, __FILE__)
|
|
80
59
|
end
|
|
81
60
|
|
|
82
|
-
def
|
|
61
|
+
def data_sets_for_one_issue issue:, today:
|
|
62
|
+
cycletime = issue.board.cycletime
|
|
63
|
+
issue_start_time, _stopped_time = cycletime.started_stopped_times(issue)
|
|
64
|
+
issue_start_date = issue_start_time.to_date
|
|
65
|
+
issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
|
|
66
|
+
[
|
|
67
|
+
status_data_sets(issue: issue, label: issue_label, today: today),
|
|
68
|
+
blocked_data_sets(
|
|
69
|
+
issue: issue,
|
|
70
|
+
issue_label: issue_label,
|
|
71
|
+
stack: 'blocked',
|
|
72
|
+
issue_start_time: issue_start_time
|
|
73
|
+
),
|
|
74
|
+
data_set_by_block(
|
|
75
|
+
issue: issue,
|
|
76
|
+
issue_label: issue_label,
|
|
77
|
+
title_label: 'Expedited',
|
|
78
|
+
stack: 'expedited',
|
|
79
|
+
color: CssVariable['--expedited-color'],
|
|
80
|
+
start_date: issue_start_date
|
|
81
|
+
) { |day| issue.expedited_on_date?(day) }
|
|
82
|
+
]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def sort_by_age! issues:, today:
|
|
86
|
+
issues.sort! do |a, b|
|
|
87
|
+
a.board.cycletime.age(b, today: today) <=> b.board.cycletime.age(a, today: today)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def select_aging_issues issues:
|
|
92
|
+
issues.select do |issue|
|
|
93
|
+
started_time, stopped_time = issue.board.cycletime.started_stopped_times(issue)
|
|
94
|
+
started_time && stopped_time.nil?
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def grow_chart_height_if_too_many_issues aging_issue_count:
|
|
83
99
|
px_per_bar = 8
|
|
84
100
|
bars_per_issue = 3
|
|
85
101
|
preferred_height = aging_issue_count * px_per_bar * bars_per_issue
|
|
@@ -89,7 +105,7 @@ class AgingWorkBarChart < ChartBase
|
|
|
89
105
|
def status_data_sets issue:, label:, today:
|
|
90
106
|
cycletime = issue.board.cycletime
|
|
91
107
|
|
|
92
|
-
issue_started_time = cycletime.
|
|
108
|
+
issue_started_time, _issue_stopped_time = cycletime.started_stopped_times(issue)
|
|
93
109
|
|
|
94
110
|
previous_start = nil
|
|
95
111
|
previous_status = nil
|
|
@@ -98,7 +114,7 @@ class AgingWorkBarChart < ChartBase
|
|
|
98
114
|
issue.changes.each do |change|
|
|
99
115
|
next unless change.status?
|
|
100
116
|
|
|
101
|
-
status = issue.
|
|
117
|
+
status = issue.find_or_create_status id: change.value_id, name: change.value
|
|
102
118
|
|
|
103
119
|
unless previous_start.nil? || previous_start < issue_started_time
|
|
104
120
|
hash = {
|
|
@@ -109,7 +125,7 @@ class AgingWorkBarChart < ChartBase
|
|
|
109
125
|
title: "#{issue.type} : #{change.value}"
|
|
110
126
|
}],
|
|
111
127
|
backgroundColor: status_category_color(status),
|
|
112
|
-
borderColor: '
|
|
128
|
+
borderColor: CssVariable['--aging-work-bar-chart-separator-color'],
|
|
113
129
|
borderWidth: {
|
|
114
130
|
top: 0,
|
|
115
131
|
right: 1,
|
|
@@ -144,10 +160,17 @@ class AgingWorkBarChart < ChartBase
|
|
|
144
160
|
end
|
|
145
161
|
|
|
146
162
|
def one_block_change_data_set starting_change:, ending_time:, issue_label:, stack:, issue_start_time:
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
169
|
+
|
|
170
|
+
color = settings['blocked_color'] || '--blocked-color'
|
|
171
|
+
color = settings['stalled_color'] || '--stalled-color' if starting_change.stalled?
|
|
149
172
|
{
|
|
150
|
-
backgroundColor: color,
|
|
173
|
+
backgroundColor: CssVariable[color],
|
|
151
174
|
data: [
|
|
152
175
|
{
|
|
153
176
|
title: starting_change.reasons,
|
|
@@ -215,6 +238,8 @@ class AgingWorkBarChart < ChartBase
|
|
|
215
238
|
}
|
|
216
239
|
end
|
|
217
240
|
|
|
241
|
+
return [] if data.empty?
|
|
242
|
+
|
|
218
243
|
{
|
|
219
244
|
type: 'bar',
|
|
220
245
|
data: data,
|
|
@@ -2,26 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
require 'jirametrics/chart_base'
|
|
4
4
|
require 'jirametrics/groupable_issue_chart'
|
|
5
|
+
require 'jirametrics/board_movement_calculator'
|
|
5
6
|
|
|
6
7
|
class AgingWorkInProgressChart < ChartBase
|
|
7
8
|
include GroupableIssueChart
|
|
8
9
|
attr_accessor :possible_statuses, :board_id
|
|
9
10
|
attr_reader :board_columns
|
|
10
11
|
|
|
11
|
-
def initialize block
|
|
12
|
+
def initialize block
|
|
12
13
|
super()
|
|
13
14
|
header_text 'Aging Work in Progress'
|
|
14
15
|
description_text <<-HTML
|
|
15
16
|
<p>
|
|
16
17
|
This chart shows only work items that have started but not completed, grouped by the column
|
|
17
|
-
|
|
18
|
+
they're currently in. Hovering over a dot will show you the ID of that work item.
|
|
18
19
|
</p>
|
|
19
20
|
<p>
|
|
20
|
-
The
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
The shaded areas indicate what percentage of the work has passed that column within that time.
|
|
22
|
+
Notes:
|
|
23
|
+
<ul>
|
|
24
|
+
<li>It only shows columns that are considered "in progress". If you see a column that wouldn't normally
|
|
25
|
+
be thought of that way, then likely issues were moving backwards or continued to progress after hitting
|
|
26
|
+
that column.</li>
|
|
27
|
+
<li>If you see a colour group that drops as it moves to the right, that generally indicates that
|
|
28
|
+
a different number of data points is being included in each column. Probably because tickets moved
|
|
29
|
+
backwards athough it could also indicate that a ticket jumped over columns as it moved to the right.
|
|
30
|
+
</li>
|
|
31
|
+
</ul>
|
|
23
32
|
</p>
|
|
33
|
+
<div style="border: 1px solid gray; padding: 0.2em">
|
|
34
|
+
<% @percentiles.keys.sort.reverse.each do |percent| %>
|
|
35
|
+
<span style="padding-left: 0.5em; padding-right: 0.5em; vertical-align: middle;"><%= color_block @percentiles[percent] %> <%= percent %>%</span>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
24
38
|
HTML
|
|
39
|
+
percentiles(
|
|
40
|
+
50 => '--aging-work-in-progress-chart-shading-50-color',
|
|
41
|
+
85 => '--aging-work-in-progress-chart-shading-85-color',
|
|
42
|
+
98 => '--aging-work-in-progress-chart-shading-98-color',
|
|
43
|
+
100 => '--aging-work-in-progress-chart-shading-100-color'
|
|
44
|
+
)
|
|
45
|
+
show_all_columns false
|
|
46
|
+
|
|
25
47
|
init_configuration_block(block) do
|
|
26
48
|
grouping_rules do |issue, rule|
|
|
27
49
|
rule.label = issue.type
|
|
@@ -35,13 +57,17 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
35
57
|
|
|
36
58
|
@header_text += " on board: #{@all_boards[@board_id].name}"
|
|
37
59
|
data_sets = make_data_sets
|
|
38
|
-
column_headings = @board_columns.collect(&:name)
|
|
39
60
|
|
|
40
|
-
adjust_visibility_of_unmapped_status_column data_sets: data_sets
|
|
61
|
+
adjust_visibility_of_unmapped_status_column data_sets: data_sets
|
|
62
|
+
adjust_chart_height
|
|
41
63
|
|
|
42
64
|
wrap_and_render(binding, __FILE__)
|
|
43
65
|
end
|
|
44
66
|
|
|
67
|
+
def show_all_columns show = true # rubocop:disable Style/OptionalBooleanParameter
|
|
68
|
+
@show_all_columns = show
|
|
69
|
+
end
|
|
70
|
+
|
|
45
71
|
def determine_board_columns
|
|
46
72
|
unmapped_statuses = current_board.possible_statuses.collect(&:id)
|
|
47
73
|
|
|
@@ -50,7 +76,7 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
50
76
|
|
|
51
77
|
@fake_column = BoardColumn.new({
|
|
52
78
|
'name' => '[Unmapped Statuses]',
|
|
53
|
-
'statuses' => unmapped_statuses.collect { |id| { 'id' => id.to_s } }.uniq
|
|
79
|
+
'statuses' => unmapped_statuses.collect { |id| { 'id' => id.to_s } }.uniq # rubocop:disable Performance/ChainArrayAllocation
|
|
54
80
|
})
|
|
55
81
|
@board_columns = columns + [@fake_column]
|
|
56
82
|
end
|
|
@@ -61,7 +87,7 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
61
87
|
board.id == @board_id && board.cycletime.in_progress?(issue)
|
|
62
88
|
end
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
@max_age = 20
|
|
65
91
|
rules_to_issues = group_issues aging_issues
|
|
66
92
|
data_sets = rules_to_issues.keys.collect do |rules|
|
|
67
93
|
{
|
|
@@ -72,7 +98,10 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
72
98
|
column = column_for issue: issue
|
|
73
99
|
next if column.nil?
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
@max_age = age if age > @max_age
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
'y' => age,
|
|
76
105
|
'x' => column.name,
|
|
77
106
|
'title' => ["#{issue.key} : #{issue.summary} (#{label_days age})"]
|
|
78
107
|
}
|
|
@@ -82,44 +111,70 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
82
111
|
'backgroundColor' => rules.color
|
|
83
112
|
}
|
|
84
113
|
end
|
|
85
|
-
data_sets << {
|
|
86
|
-
'type' => 'bar',
|
|
87
|
-
'label' => "#{percentage}%",
|
|
88
|
-
'barPercentage' => 1.0,
|
|
89
|
-
'categoryPercentage' => 1.0,
|
|
90
|
-
'data' => days_at_percentage_threshold_for_all_columns(percentage: percentage, issues: @issues).drop(1)
|
|
91
|
-
}
|
|
92
|
-
end
|
|
93
114
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
calculator = BoardMovementCalculator.new board: @all_boards[@board_id], issues: issues, today: date_range.end
|
|
116
|
+
|
|
117
|
+
column_indexes_to_remove = []
|
|
118
|
+
unless @show_all_columns
|
|
119
|
+
column_indexes_to_remove = indexes_of_leading_and_trailing_zeros(calculator.age_data_for(percentage: 100))
|
|
120
|
+
|
|
121
|
+
column_indexes_to_remove.reverse_each do |index|
|
|
122
|
+
@board_columns.delete_at index
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
@row_index_offset = data_sets.size
|
|
127
|
+
|
|
128
|
+
bar_data = []
|
|
129
|
+
calculator.stacked_age_data_for(percentages: @percentiles.keys).each do |percentage, data|
|
|
130
|
+
column_indexes_to_remove.reverse_each { |index| data.delete_at index }
|
|
131
|
+
color = @percentiles[percentage]
|
|
132
|
+
|
|
133
|
+
data_sets << {
|
|
134
|
+
'type' => 'bar',
|
|
135
|
+
'label' => "#{percentage}%",
|
|
136
|
+
'barPercentage' => 1.0,
|
|
137
|
+
'categoryPercentage' => 1.0,
|
|
138
|
+
'backgroundColor' => color,
|
|
139
|
+
'data' => data
|
|
140
|
+
}
|
|
141
|
+
bar_data << data
|
|
99
142
|
end
|
|
143
|
+
@bar_data = adjust_bar_data bar_data
|
|
144
|
+
|
|
145
|
+
data_sets
|
|
100
146
|
end
|
|
101
147
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
def adjust_bar_data input
|
|
149
|
+
return [] if input.empty?
|
|
150
|
+
|
|
151
|
+
row_size = input.first.size
|
|
106
152
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
153
|
+
output = []
|
|
154
|
+
output << input.first
|
|
155
|
+
input.drop(1).each do |row|
|
|
156
|
+
previous_row = output.last
|
|
157
|
+
output << 0.upto(row_size - 1).collect { |i| row[i] + previous_row[i] }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
output
|
|
110
161
|
end
|
|
111
162
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
163
|
+
def indexes_of_leading_and_trailing_zeros list
|
|
164
|
+
result = []
|
|
165
|
+
0.upto(list.size - 1) do |index|
|
|
166
|
+
break unless list[index].zero?
|
|
167
|
+
|
|
168
|
+
result << index
|
|
169
|
+
end
|
|
116
170
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
171
|
+
stop_at = result.empty? ? 0 : (result.last + 1)
|
|
172
|
+
(list.size - 1).downto(stop_at).each do |index|
|
|
173
|
+
break unless list[index].zero?
|
|
120
174
|
|
|
121
|
-
|
|
175
|
+
result << index if list[index].zero?
|
|
122
176
|
end
|
|
177
|
+
result
|
|
123
178
|
end
|
|
124
179
|
|
|
125
180
|
def column_for issue:
|
|
@@ -128,7 +183,7 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
128
183
|
end
|
|
129
184
|
end
|
|
130
185
|
|
|
131
|
-
def adjust_visibility_of_unmapped_status_column data_sets
|
|
186
|
+
def adjust_visibility_of_unmapped_status_column data_sets:
|
|
132
187
|
column_name = @fake_column.name
|
|
133
188
|
|
|
134
189
|
has_unmapped = data_sets.any? do |set|
|
|
@@ -141,8 +196,19 @@ class AgingWorkInProgressChart < ChartBase
|
|
|
141
196
|
@description_text += "<p>The items shown in #{column_name.inspect} are not visible on the " \
|
|
142
197
|
'board but are still active. Most likely everyone has forgotten about them.</p>'
|
|
143
198
|
else
|
|
144
|
-
column_headings.pop
|
|
199
|
+
# @column_headings.pop
|
|
145
200
|
@board_columns.pop
|
|
146
201
|
end
|
|
147
202
|
end
|
|
203
|
+
|
|
204
|
+
def percentiles percentile_color_hash
|
|
205
|
+
@percentiles = percentile_color_hash.transform_values { |value| CssVariable[value] }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def adjust_chart_height
|
|
209
|
+
min_height = @max_age * 5
|
|
210
|
+
|
|
211
|
+
@canvas_height = min_height if min_height > @canvas_height
|
|
212
|
+
@canvas_height = 400 if min_height > 400
|
|
213
|
+
end
|
|
148
214
|
end
|