jirametrics 2.10 → 2.30
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/bin/jirametrics-mcp +5 -0
- data/lib/jirametrics/aggregate_config.rb +10 -2
- data/lib/jirametrics/aging_work_bar_chart.rb +191 -133
- data/lib/jirametrics/aging_work_in_progress_chart.rb +138 -42
- data/lib/jirametrics/aging_work_table.rb +62 -17
- data/lib/jirametrics/anonymizer.rb +81 -6
- data/lib/jirametrics/atlassian_document_format.rb +160 -0
- data/lib/jirametrics/bar_chart_range.rb +17 -0
- data/lib/jirametrics/blocked_stalled_change.rb +5 -3
- data/lib/jirametrics/board.rb +63 -11
- data/lib/jirametrics/board_config.rb +5 -1
- data/lib/jirametrics/board_feature.rb +14 -0
- data/lib/jirametrics/board_movement_calculator.rb +155 -0
- data/lib/jirametrics/cfd_data_builder.rb +108 -0
- data/lib/jirametrics/change_item.rb +49 -19
- data/lib/jirametrics/chart_base.rb +147 -7
- data/lib/jirametrics/css_variable.rb +2 -2
- data/lib/jirametrics/cumulative_flow_diagram.rb +208 -0
- data/lib/jirametrics/{cycletime_config.rb → cycle_time_config.rb} +22 -5
- data/lib/jirametrics/cycletime_histogram.rb +15 -101
- data/lib/jirametrics/cycletime_scatterplot.rb +17 -83
- data/lib/jirametrics/daily_view.rb +306 -0
- data/lib/jirametrics/daily_wip_by_age_chart.rb +4 -5
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +14 -4
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +4 -2
- data/lib/jirametrics/daily_wip_chart.rb +30 -8
- data/lib/jirametrics/data_quality_report.rb +43 -12
- data/lib/jirametrics/dependency_chart.rb +6 -3
- data/lib/jirametrics/download_config.rb +15 -0
- data/lib/jirametrics/downloader.rb +128 -71
- data/lib/jirametrics/downloader_for_cloud.rb +287 -0
- data/lib/jirametrics/downloader_for_data_center.rb +95 -0
- data/lib/jirametrics/estimate_accuracy_chart.rb +74 -12
- data/lib/jirametrics/estimation_configuration.rb +25 -0
- data/lib/jirametrics/examples/aggregated_project.rb +2 -2
- data/lib/jirametrics/examples/standard_project.rb +42 -27
- data/lib/jirametrics/expedited_chart.rb +3 -1
- data/lib/jirametrics/exporter.rb +28 -8
- data/lib/jirametrics/file_config.rb +10 -12
- data/lib/jirametrics/file_system.rb +59 -3
- data/lib/jirametrics/fix_version.rb +13 -0
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +6 -2
- data/lib/jirametrics/github_gateway.rb +115 -0
- data/lib/jirametrics/groupable_issue_chart.rb +11 -1
- data/lib/jirametrics/grouping_rules.rb +26 -4
- data/lib/jirametrics/html/aging_work_bar_chart.erb +5 -5
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +24 -5
- data/lib/jirametrics/html/aging_work_table.erb +12 -3
- data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
- data/lib/jirametrics/html/cumulative_flow_diagram.erb +503 -0
- data/lib/jirametrics/html/daily_wip_chart.erb +40 -5
- data/lib/jirametrics/html/estimate_accuracy_chart.erb +4 -12
- data/lib/jirametrics/html/expedited_chart.erb +6 -14
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +4 -8
- data/lib/jirametrics/html/index.css +323 -63
- data/lib/jirametrics/html/index.erb +17 -19
- data/lib/jirametrics/html/index.js +164 -0
- data/lib/jirametrics/html/legacy_colors.css +174 -0
- data/lib/jirametrics/html/sprint_burndown.erb +17 -15
- data/lib/jirametrics/html/throughput_chart.erb +42 -11
- data/lib/jirametrics/html/{cycletime_histogram.erb → time_based_histogram.erb} +61 -59
- data/lib/jirametrics/html/{cycletime_scatterplot.erb → time_based_scatterplot.erb} +15 -11
- data/lib/jirametrics/html/wip_by_column_chart.erb +250 -0
- data/lib/jirametrics/html_generator.rb +32 -0
- data/lib/jirametrics/html_report_config.rb +52 -55
- data/lib/jirametrics/issue.rb +347 -103
- data/lib/jirametrics/issue_collection.rb +33 -0
- data/lib/jirametrics/issue_printer.rb +97 -0
- data/lib/jirametrics/jira_gateway.rb +81 -14
- data/lib/jirametrics/mcp_server.rb +531 -0
- data/lib/jirametrics/project_config.rb +151 -18
- data/lib/jirametrics/pull_request.rb +30 -0
- data/lib/jirametrics/pull_request_cycle_time_histogram.rb +77 -0
- data/lib/jirametrics/pull_request_cycle_time_scatterplot.rb +88 -0
- data/lib/jirametrics/pull_request_review.rb +13 -0
- data/lib/jirametrics/raw_javascript.rb +17 -0
- data/lib/jirametrics/settings.json +6 -1
- data/lib/jirametrics/sprint.rb +13 -0
- data/lib/jirametrics/sprint_burndown.rb +45 -37
- data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
- data/lib/jirametrics/status.rb +3 -0
- data/lib/jirametrics/status_collection.rb +7 -0
- data/lib/jirametrics/stitcher.rb +81 -0
- data/lib/jirametrics/throughput_by_completed_resolution_chart.rb +22 -0
- data/lib/jirametrics/throughput_chart.rb +73 -23
- data/lib/jirametrics/time_based_histogram.rb +139 -0
- data/lib/jirametrics/time_based_scatterplot.rb +107 -0
- data/lib/jirametrics/user.rb +12 -0
- data/lib/jirametrics/wip_by_column_chart.rb +236 -0
- data/lib/jirametrics.rb +83 -64
- metadata +66 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1f64f63f13e8cb59d3b18fb1e1ad90f77ca06d0e2f59a75ff7b7bae4db1870f
|
|
4
|
+
data.tar.gz: 9b7d6b8759102d7590e86c114d2ac8b1b2e7c4cc9f45a168002752196a6bf797
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ec0bee468f8c34c001ea9151b0d78b1018d246cc86f9c2588a70ee55e3940b6263f69032884ec5c2dc596d3182909829f6ee63fe51b6c65444ce667bf70a6ca
|
|
7
|
+
data.tar.gz: 9b9f5337d0fc671639f9f651977cb2ecc60b2d27105cdce1b75a2a4188c093c111ba29ed880adccb515d548b8bd9209b4cf703482a0ede4ccea6b182212a3a57
|
data/bin/jirametrics-mcp
ADDED
|
@@ -65,8 +65,16 @@ class AggregateConfig
|
|
|
65
65
|
|
|
66
66
|
if issues.nil?
|
|
67
67
|
file_system.warning "No issues found for #{project_name}"
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@project_config.add_issues issues
|
|
72
|
+
|
|
73
|
+
# Bring fix versions over
|
|
74
|
+
project.fix_versions.each do |fix_version|
|
|
75
|
+
unless @project_config.fix_versions.find { |fv| fv.id == fix_version.id }
|
|
76
|
+
@project_config.fix_versions << fix_version
|
|
77
|
+
end
|
|
70
78
|
end
|
|
71
79
|
end
|
|
72
80
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'jirametrics/chart_base'
|
|
4
|
+
require 'jirametrics/bar_chart_range'
|
|
4
5
|
|
|
5
6
|
class AgingWorkBarChart < ChartBase
|
|
6
7
|
def initialize block
|
|
7
8
|
super()
|
|
8
9
|
|
|
10
|
+
@age_cutoff = nil
|
|
9
11
|
header_text 'Aging Work Bar Chart'
|
|
10
12
|
description_text <<-HTML
|
|
11
13
|
<p>
|
|
@@ -13,16 +15,19 @@ class AgingWorkBarChart < ChartBase
|
|
|
13
15
|
newest at the bottom.
|
|
14
16
|
</p>
|
|
15
17
|
<p>
|
|
16
|
-
There are
|
|
17
|
-
information relevant to that. Hovering over any of the bars will provide more details.
|
|
18
|
+
There are <%= (aggregated_project? || current_board.scrum?) ? 'four' : 'three' %> bars for each issue, and hovering over any of the bars will provide more details.
|
|
18
19
|
<ol>
|
|
19
|
-
<li>The
|
|
20
|
+
<li>Status: The status the issue was in at any time. The colour indicates the
|
|
20
21
|
status category, which will be one of #{color_block '--status-category-todo-color'} To Do,
|
|
21
22
|
#{color_block '--status-category-inprogress-color'} In Progress,
|
|
22
23
|
or #{color_block '--status-category-done-color'} Done</li>
|
|
23
|
-
<li>
|
|
24
|
+
<li>Activity: This bar indicates #{color_block '--blocked-color'} blocked
|
|
24
25
|
or #{color_block '--stalled-color'} stalled.</li>
|
|
25
|
-
<li>
|
|
26
|
+
<li>Priority: This shows the priority over time. If one of these priorities is considered expedited
|
|
27
|
+
then it will be drawn with diagonal lines.</li>
|
|
28
|
+
<% if aggregated_project? || current_board.scrum? %>
|
|
29
|
+
<li>Sprints: The sprints that the issue was in.</li>
|
|
30
|
+
<% end %>
|
|
26
31
|
</ol>
|
|
27
32
|
</p>
|
|
28
33
|
#{describe_non_working_days}
|
|
@@ -36,6 +41,7 @@ class AgingWorkBarChart < ChartBase
|
|
|
36
41
|
|
|
37
42
|
def run
|
|
38
43
|
aging_issues = select_aging_issues issues: @issues
|
|
44
|
+
adjust_time_date_ranges_to_start_from_earliest_issue_start(aging_issues)
|
|
39
45
|
|
|
40
46
|
today = date_range.end
|
|
41
47
|
sort_by_age! issues: aging_issues, today: today
|
|
@@ -58,134 +64,134 @@ class AgingWorkBarChart < ChartBase
|
|
|
58
64
|
wrap_and_render(binding, __FILE__)
|
|
59
65
|
end
|
|
60
66
|
|
|
67
|
+
def adjust_time_date_ranges_to_start_from_earliest_issue_start aging_issues
|
|
68
|
+
earliest_start_time = aging_issues.collect do |issue|
|
|
69
|
+
issue.started_stopped_times.first
|
|
70
|
+
end.min
|
|
71
|
+
return if earliest_start_time.nil? || earliest_start_time >= @time_range.begin
|
|
72
|
+
|
|
73
|
+
@time_range = earliest_start_time..@time_range.end
|
|
74
|
+
@date_range = @time_range.begin.to_date..@time_range.end.to_date
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
def data_sets_for_one_issue issue:, today:
|
|
62
78
|
cycletime = issue.board.cycletime
|
|
63
|
-
issue_start_time
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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) }
|
|
79
|
+
issue_start_time = cycletime.started_stopped_times(issue).first
|
|
80
|
+
end_of_today = Time.parse("#{today}T23:59:59#{@timezone_offset}")
|
|
81
|
+
|
|
82
|
+
bar_data = [
|
|
83
|
+
['status', collect_status_ranges(issue: issue, now: end_of_today)],
|
|
84
|
+
['blocked', collect_blocked_stalled_ranges(issue: issue, issue_start_time: issue_start_time)],
|
|
85
|
+
['priority', collect_priority_ranges(issue: issue)]
|
|
82
86
|
]
|
|
87
|
+
bar_data << ['sprints', collect_sprint_ranges(issue: issue)] if aggregated_project? || current_board.scrum?
|
|
88
|
+
|
|
89
|
+
bar_data.each { |entry| clip_ranges_to_start_time(ranges: entry.last, issue_start_time: issue_start_time) }
|
|
90
|
+
|
|
91
|
+
issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
|
|
92
|
+
bar_data.collect do |stack, ranges|
|
|
93
|
+
bar_chart_range_to_data_set y_value: issue_label, ranges: ranges, stack: stack, issue_start_time: issue_start_time
|
|
94
|
+
end
|
|
83
95
|
end
|
|
84
96
|
|
|
85
97
|
def sort_by_age! issues:, today:
|
|
86
98
|
issues.sort! do |a, b|
|
|
87
|
-
|
|
99
|
+
b.board.cycletime.age(b, today: today) <=> a.board.cycletime.age(a, today: today)
|
|
88
100
|
end
|
|
89
101
|
end
|
|
90
102
|
|
|
91
103
|
def select_aging_issues issues:
|
|
92
104
|
issues.select do |issue|
|
|
93
|
-
started_time, stopped_time = issue.
|
|
94
|
-
started_time && stopped_time.nil?
|
|
105
|
+
started_time, stopped_time = issue.started_stopped_times
|
|
106
|
+
next false unless started_time && stopped_time.nil?
|
|
107
|
+
|
|
108
|
+
age = (date_range.end - started_time.to_date).to_i + 1
|
|
109
|
+
!(@age_cutoff && @age_cutoff >= age)
|
|
95
110
|
end
|
|
96
111
|
end
|
|
97
112
|
|
|
98
113
|
def grow_chart_height_if_too_many_issues aging_issue_count:
|
|
99
|
-
px_per_bar =
|
|
114
|
+
px_per_bar = 10
|
|
100
115
|
bars_per_issue = 3
|
|
116
|
+
bars_per_issue += 1 if aggregated_project? || current_board.scrum?
|
|
117
|
+
|
|
101
118
|
preferred_height = aging_issue_count * px_per_bar * bars_per_issue
|
|
102
119
|
@canvas_height = preferred_height if @canvas_height.nil? || @canvas_height < preferred_height
|
|
103
120
|
end
|
|
104
121
|
|
|
105
|
-
def
|
|
106
|
-
|
|
122
|
+
def clip_ranges_to_start_time ranges:, issue_start_time:
|
|
123
|
+
return if issue_start_time.nil?
|
|
107
124
|
|
|
108
|
-
|
|
125
|
+
ranges.each { |range| range.start = issue_start_time if range.start < issue_start_time }
|
|
126
|
+
ranges.reject! { |range| range.start >= range.stop }
|
|
127
|
+
end
|
|
109
128
|
|
|
129
|
+
def collect_status_ranges issue:, now:
|
|
130
|
+
ranges = []
|
|
131
|
+
issue_started_time = issue.started_stopped_times.first
|
|
110
132
|
previous_start = nil
|
|
111
133
|
previous_status = nil
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
unless previous_start.nil? || previous_start < issue_started_time
|
|
120
|
-
hash = {
|
|
121
|
-
type: 'bar',
|
|
122
|
-
data: [{
|
|
123
|
-
x: [chart_format(previous_start), chart_format(change.time)],
|
|
124
|
-
y: label,
|
|
125
|
-
title: "#{issue.type} : #{change.value}"
|
|
126
|
-
}],
|
|
127
|
-
backgroundColor: status_category_color(status),
|
|
128
|
-
borderColor: CssVariable['--aging-work-bar-chart-separator-color'],
|
|
129
|
-
borderWidth: {
|
|
130
|
-
top: 0,
|
|
131
|
-
right: 1,
|
|
132
|
-
bottom: 0,
|
|
133
|
-
left: 0
|
|
134
|
-
},
|
|
135
|
-
stacked: true,
|
|
136
|
-
stack: 'status'
|
|
137
|
-
}
|
|
138
|
-
data_sets << hash if date_range.include?(change.time.to_date)
|
|
134
|
+
issue.status_changes.each do |change|
|
|
135
|
+
new_status = issue.find_or_create_status id: change.value_id, name: change.value
|
|
136
|
+
if previous_start.nil?
|
|
137
|
+
previous_start = change.time
|
|
138
|
+
previous_status = new_status
|
|
139
|
+
next
|
|
139
140
|
end
|
|
140
141
|
|
|
142
|
+
previous_start = issue_started_time if issue_started_time > previous_start
|
|
143
|
+
|
|
144
|
+
ranges << BarChartRange.new(
|
|
145
|
+
start: previous_start,
|
|
146
|
+
stop: change.time,
|
|
147
|
+
color: status_category_color(previous_status),
|
|
148
|
+
title: previous_status.to_s
|
|
149
|
+
)
|
|
141
150
|
previous_start = change.time
|
|
142
|
-
previous_status =
|
|
151
|
+
previous_status = new_status
|
|
143
152
|
end
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
ranges << BarChartRange.new(
|
|
155
|
+
start: previous_start,
|
|
156
|
+
stop: now,
|
|
157
|
+
color: status_category_color(previous_status),
|
|
158
|
+
title: previous_status.to_s
|
|
159
|
+
)
|
|
160
|
+
ranges
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def bar_chart_range_to_data_set y_value:, ranges:, stack:, issue_start_time:
|
|
164
|
+
ranges.filter_map do |bar_chart_range|
|
|
165
|
+
next if bar_chart_range.stop < issue_start_time
|
|
166
|
+
|
|
167
|
+
background_color = bar_chart_range.color
|
|
168
|
+
if bar_chart_range.highlight
|
|
169
|
+
background_color = RawJavascript.new("createDiagonalPattern(#{background_color.to_json})")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
{
|
|
147
173
|
type: 'bar',
|
|
148
174
|
data: [{
|
|
149
|
-
x: [chart_format(
|
|
150
|
-
y:
|
|
151
|
-
title:
|
|
175
|
+
x: [chart_format([bar_chart_range.start, issue_start_time].max), chart_format(bar_chart_range.stop)],
|
|
176
|
+
y: y_value,
|
|
177
|
+
title: bar_chart_range.title
|
|
152
178
|
}],
|
|
153
|
-
backgroundColor:
|
|
179
|
+
backgroundColor: background_color,
|
|
180
|
+
borderColor: CssVariable['--aging-work-bar-chart-separator-color'],
|
|
181
|
+
borderWidth: {
|
|
182
|
+
top: 0,
|
|
183
|
+
right: 1,
|
|
184
|
+
bottom: 0,
|
|
185
|
+
left: 0
|
|
186
|
+
},
|
|
154
187
|
stacked: true,
|
|
155
|
-
stack:
|
|
188
|
+
stack: stack
|
|
156
189
|
}
|
|
157
190
|
end
|
|
158
|
-
|
|
159
|
-
data_sets
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def one_block_change_data_set starting_change:, ending_time:, issue_label:, stack:, issue_start_time:
|
|
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?
|
|
172
|
-
{
|
|
173
|
-
backgroundColor: CssVariable[color],
|
|
174
|
-
data: [
|
|
175
|
-
{
|
|
176
|
-
title: starting_change.reasons,
|
|
177
|
-
x: [chart_format([issue_start_time, starting_change.time].max), chart_format(ending_time)],
|
|
178
|
-
y: issue_label
|
|
179
|
-
}
|
|
180
|
-
],
|
|
181
|
-
stack: stack,
|
|
182
|
-
stacked: true,
|
|
183
|
-
type: 'bar'
|
|
184
|
-
}
|
|
185
191
|
end
|
|
186
192
|
|
|
187
|
-
def
|
|
188
|
-
|
|
193
|
+
def collect_blocked_stalled_ranges issue:, issue_start_time:
|
|
194
|
+
results = []
|
|
189
195
|
starting_change = nil
|
|
190
196
|
|
|
191
197
|
issue.blocked_stalled_changes(end_time: time_range.end).each do |change|
|
|
@@ -195,58 +201,106 @@ class AgingWorkBarChart < ChartBase
|
|
|
195
201
|
end
|
|
196
202
|
|
|
197
203
|
if change.time >= issue_start_time
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
color = settings['blocked_color'] || '--blocked-color'
|
|
205
|
+
color = settings['stalled_color'] || '--stalled-color' if starting_change.stalled?
|
|
206
|
+
|
|
207
|
+
results << BarChartRange.new(
|
|
208
|
+
start: starting_change.time, stop: change.time, color: CssVariable[color], title: starting_change.reasons
|
|
201
209
|
)
|
|
202
210
|
end
|
|
203
211
|
|
|
204
212
|
starting_change = change
|
|
205
213
|
end
|
|
214
|
+
results
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def collect_priority_ranges issue:
|
|
218
|
+
expedited_priority_names = settings['expedited_priority_names']
|
|
219
|
+
|
|
220
|
+
previous_change = nil
|
|
221
|
+
results = []
|
|
222
|
+
|
|
223
|
+
issue.changes.each do |change|
|
|
224
|
+
next unless change.priority?
|
|
225
|
+
|
|
226
|
+
if previous_change.nil?
|
|
227
|
+
previous_change = change
|
|
228
|
+
next
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
results << create_range_for_priority(
|
|
232
|
+
previous_change: previous_change, stop_time: change.time,
|
|
233
|
+
expedited_priority_names: expedited_priority_names
|
|
234
|
+
)
|
|
235
|
+
previous_change = change
|
|
236
|
+
end
|
|
206
237
|
|
|
207
|
-
|
|
238
|
+
if previous_change
|
|
239
|
+
results << create_range_for_priority(
|
|
240
|
+
previous_change: previous_change, stop_time: time_range.end,
|
|
241
|
+
expedited_priority_names: expedited_priority_names
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
results
|
|
208
245
|
end
|
|
209
246
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
def collect_sprint_ranges issue:
|
|
248
|
+
results = []
|
|
249
|
+
open_sprints = {}
|
|
250
|
+
|
|
251
|
+
issue.changes.each do |change|
|
|
252
|
+
next unless change.sprint?
|
|
253
|
+
|
|
254
|
+
removed_sprint_ids = change.old_value_id - change.value_id
|
|
255
|
+
added_sprint_ids = change.value_id - change.old_value_id
|
|
256
|
+
|
|
257
|
+
removed_sprint_ids.each do |id|
|
|
258
|
+
data = open_sprints.delete(id)
|
|
259
|
+
next unless data
|
|
260
|
+
|
|
261
|
+
completed = data[:sprint].completed_time
|
|
262
|
+
stop = completed ? [change.time, completed].min : change.time
|
|
263
|
+
results << BarChartRange.new(
|
|
264
|
+
start: data[:start_time], stop: stop,
|
|
265
|
+
color: CssVariable['--sprint-color'], title: data[:sprint].name
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
added_sprint_ids.each do |id|
|
|
270
|
+
sprint = issue.board.sprints.find { |s| s.id == id }
|
|
271
|
+
next unless sprint
|
|
272
|
+
next if sprint.future?
|
|
273
|
+
|
|
274
|
+
start_time = [sprint.start_time, change.time].max
|
|
275
|
+
open_sprints[id] = { start_time: start_time, sprint: sprint }
|
|
230
276
|
end
|
|
231
277
|
end
|
|
232
278
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
279
|
+
open_sprints.each_value do |data|
|
|
280
|
+
next if data[:sprint].future?
|
|
281
|
+
|
|
282
|
+
stop = data[:sprint].completed_time || time_range.end
|
|
283
|
+
results << BarChartRange.new(
|
|
284
|
+
start: data[:start_time], stop: stop,
|
|
285
|
+
color: CssVariable['--sprint-color'], title: data[:sprint].name
|
|
286
|
+
)
|
|
239
287
|
end
|
|
240
288
|
|
|
241
|
-
|
|
289
|
+
results
|
|
290
|
+
end
|
|
242
291
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
292
|
+
def create_range_for_priority previous_change:, stop_time:, expedited_priority_names:
|
|
293
|
+
expedited = expedited_priority_names.include?(previous_change.value)
|
|
294
|
+
title = "Priority: #{previous_change.value}"
|
|
295
|
+
title << ' (expedited)' if expedited
|
|
296
|
+
|
|
297
|
+
BarChartRange.new(
|
|
298
|
+
start: previous_change.time,
|
|
299
|
+
stop: stop_time,
|
|
300
|
+
color: CssVariable["--priority-color-#{previous_change.value.downcase.gsub(/\s/, '')}"],
|
|
301
|
+
title: title,
|
|
302
|
+
highlight: expedited
|
|
303
|
+
)
|
|
250
304
|
end
|
|
251
305
|
|
|
252
306
|
def calculate_percent_line percentage: 85
|
|
@@ -255,4 +309,8 @@ class AgingWorkBarChart < ChartBase
|
|
|
255
309
|
|
|
256
310
|
days[days.length * percentage / 100]
|
|
257
311
|
end
|
|
312
|
+
|
|
313
|
+
def age_cutoff days
|
|
314
|
+
@age_cutoff = days
|
|
315
|
+
end
|
|
258
316
|
end
|