jirametrics 2.5 → 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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/bin/jirametrics-mcp +5 -0
  3. data/lib/jirametrics/aggregate_config.rb +16 -3
  4. data/lib/jirametrics/aging_work_bar_chart.rb +193 -133
  5. data/lib/jirametrics/aging_work_in_progress_chart.rb +138 -42
  6. data/lib/jirametrics/aging_work_table.rb +63 -19
  7. data/lib/jirametrics/anonymizer.rb +81 -6
  8. data/lib/jirametrics/atlassian_document_format.rb +160 -0
  9. data/lib/jirametrics/bar_chart_range.rb +17 -0
  10. data/lib/jirametrics/blocked_stalled_change.rb +6 -4
  11. data/lib/jirametrics/board.rb +73 -20
  12. data/lib/jirametrics/board_config.rb +10 -2
  13. data/lib/jirametrics/board_feature.rb +14 -0
  14. data/lib/jirametrics/board_movement_calculator.rb +155 -0
  15. data/lib/jirametrics/cfd_data_builder.rb +108 -0
  16. data/lib/jirametrics/change_item.rb +54 -18
  17. data/lib/jirametrics/chart_base.rb +203 -30
  18. data/lib/jirametrics/css_variable.rb +2 -2
  19. data/lib/jirametrics/cumulative_flow_diagram.rb +208 -0
  20. data/lib/jirametrics/cycle_time_config.rb +137 -0
  21. data/lib/jirametrics/cycletime_histogram.rb +17 -38
  22. data/lib/jirametrics/cycletime_scatterplot.rb +18 -87
  23. data/lib/jirametrics/daily_view.rb +306 -0
  24. data/lib/jirametrics/daily_wip_by_age_chart.rb +5 -8
  25. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +15 -5
  26. data/lib/jirametrics/daily_wip_by_parent_chart.rb +4 -6
  27. data/lib/jirametrics/daily_wip_chart.rb +36 -16
  28. data/lib/jirametrics/data_quality_report.rb +251 -42
  29. data/lib/jirametrics/dependency_chart.rb +8 -6
  30. data/lib/jirametrics/download_config.rb +17 -2
  31. data/lib/jirametrics/downloader.rb +177 -108
  32. data/lib/jirametrics/downloader_for_cloud.rb +287 -0
  33. data/lib/jirametrics/downloader_for_data_center.rb +95 -0
  34. data/lib/jirametrics/estimate_accuracy_chart.rb +75 -14
  35. data/lib/jirametrics/estimation_configuration.rb +25 -0
  36. data/lib/jirametrics/examples/aggregated_project.rb +5 -8
  37. data/lib/jirametrics/examples/standard_project.rb +54 -38
  38. data/lib/jirametrics/expedited_chart.rb +10 -9
  39. data/lib/jirametrics/exporter.rb +51 -16
  40. data/lib/jirametrics/file_config.rb +21 -6
  41. data/lib/jirametrics/file_system.rb +96 -4
  42. data/lib/jirametrics/fix_version.rb +13 -0
  43. data/lib/jirametrics/flow_efficiency_scatterplot.rb +115 -0
  44. data/lib/jirametrics/github_gateway.rb +115 -0
  45. data/lib/jirametrics/groupable_issue_chart.rb +12 -4
  46. data/lib/jirametrics/grouping_rules.rb +26 -4
  47. data/lib/jirametrics/html/aging_work_bar_chart.erb +8 -17
  48. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +24 -5
  49. data/lib/jirametrics/html/aging_work_table.erb +13 -4
  50. data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
  51. data/lib/jirametrics/html/cumulative_flow_diagram.erb +503 -0
  52. data/lib/jirametrics/html/daily_wip_chart.erb +41 -15
  53. data/lib/jirametrics/html/estimate_accuracy_chart.erb +4 -12
  54. data/lib/jirametrics/html/expedited_chart.erb +7 -24
  55. data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +81 -0
  56. data/lib/jirametrics/html/hierarchy_table.erb +1 -1
  57. data/lib/jirametrics/html/index.css +336 -62
  58. data/lib/jirametrics/html/index.erb +16 -21
  59. data/lib/jirametrics/html/index.js +164 -0
  60. data/lib/jirametrics/html/legacy_colors.css +174 -0
  61. data/lib/jirametrics/html/sprint_burndown.erb +18 -25
  62. data/lib/jirametrics/html/throughput_chart.erb +43 -21
  63. data/lib/jirametrics/html/time_based_histogram.erb +123 -0
  64. data/lib/jirametrics/html/{cycletime_scatterplot.erb → time_based_scatterplot.erb} +16 -21
  65. data/lib/jirametrics/html/wip_by_column_chart.erb +250 -0
  66. data/lib/jirametrics/html_generator.rb +32 -0
  67. data/lib/jirametrics/html_report_config.rb +83 -76
  68. data/lib/jirametrics/issue.rb +481 -97
  69. data/lib/jirametrics/issue_collection.rb +33 -0
  70. data/lib/jirametrics/issue_printer.rb +97 -0
  71. data/lib/jirametrics/jira_gateway.rb +96 -16
  72. data/lib/jirametrics/mcp_server.rb +531 -0
  73. data/lib/jirametrics/project_config.rb +374 -130
  74. data/lib/jirametrics/pull_request.rb +30 -0
  75. data/lib/jirametrics/pull_request_cycle_time_histogram.rb +77 -0
  76. data/lib/jirametrics/pull_request_cycle_time_scatterplot.rb +88 -0
  77. data/lib/jirametrics/pull_request_review.rb +13 -0
  78. data/lib/jirametrics/raw_javascript.rb +17 -0
  79. data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
  80. data/lib/jirametrics/settings.json +7 -1
  81. data/lib/jirametrics/sprint.rb +13 -0
  82. data/lib/jirametrics/sprint_burndown.rb +47 -39
  83. data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
  84. data/lib/jirametrics/status.rb +84 -19
  85. data/lib/jirametrics/status_collection.rb +83 -38
  86. data/lib/jirametrics/stitcher.rb +81 -0
  87. data/lib/jirametrics/throughput_by_completed_resolution_chart.rb +22 -0
  88. data/lib/jirametrics/throughput_chart.rb +73 -23
  89. data/lib/jirametrics/time_based_histogram.rb +139 -0
  90. data/lib/jirametrics/time_based_scatterplot.rb +107 -0
  91. data/lib/jirametrics/user.rb +12 -0
  92. data/lib/jirametrics/value_equality.rb +2 -2
  93. data/lib/jirametrics/wip_by_column_chart.rb +236 -0
  94. data/lib/jirametrics.rb +101 -66
  95. metadata +72 -16
  96. data/lib/jirametrics/cycletime_config.rb +0 -69
  97. data/lib/jirametrics/discard_changes_before.rb +0 -37
  98. data/lib/jirametrics/html/cycletime_histogram.erb +0 -47
  99. data/lib/jirametrics/html/data_quality_report.erb +0 -126
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22daa10ce05dd421522069d3b5d73868f8f7bc1a6a53128acab515ca98644892
4
- data.tar.gz: 8d797de1b80fe8f35fde593bc578012af92080e32000a905ea90ed286754bccd
3
+ metadata.gz: a1f64f63f13e8cb59d3b18fb1e1ad90f77ca06d0e2f59a75ff7b7bae4db1870f
4
+ data.tar.gz: 9b7d6b8759102d7590e86c114d2ac8b1b2e7c4cc9f45a168002752196a6bf797
5
5
  SHA512:
6
- metadata.gz: b00a94c9c676625ff698ecf61211dea8b8bbc6d3fde400e23f349a888147b0245616f8944646eac4435106d1156dca08327fe596dcc0402e92169c67cc72cdb1
7
- data.tar.gz: c85706281f71b2b0adf5dae8f99bcab478dadeb86b014ce88874879051ac0e4288d3d7cda1e0fd949e03b70878969f918248649423704ae5d757f63c83b12733
6
+ metadata.gz: 8ec0bee468f8c34c001ea9151b0d78b1018d246cc86f9c2588a70ee55e3940b6263f69032884ec5c2dc596d3182909829f6ee63fe51b6c65444ce667bf70a6ca
7
+ data.tar.gz: 9b9f5337d0fc671639f9f651977cb2ecc60b2d27105cdce1b75a2a4188c093c111ba29ed880adccb515d548b8bd9209b4cf703482a0ede4ccea6b182212a3a57
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'jirametrics'
5
+ JiraMetrics.start(['mcp'] + ARGV)
@@ -41,7 +41,7 @@ class AggregateConfig
41
41
  def include_issues_from project_name
42
42
  project = @project_config.exporter.project_configs.find { |p| p.name == project_name }
43
43
  if project.nil?
44
- log "Warning: Aggregated project #{@project_config.name.inspect} is attempting to load " \
44
+ file_system.warning "Aggregated project #{@project_config.name.inspect} is attempting to load " \
45
45
  "project #{project_name.inspect} but it can't be found. Is it disabled?"
46
46
  return
47
47
  end
@@ -62,7 +62,20 @@ class AggregateConfig
62
62
  'the first file section'
63
63
  end
64
64
  end
65
+
66
+ if issues.nil?
67
+ file_system.warning "No issues found for #{project_name}"
68
+ return
69
+ end
70
+
65
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
78
+ end
66
79
  end
67
80
 
68
81
  def find_time_range projects:
@@ -81,7 +94,7 @@ class AggregateConfig
81
94
 
82
95
  private
83
96
 
84
- def log message
85
- @project_config.exporter.file_system.log message
97
+ def file_system
98
+ @project_config.exporter.file_system
86
99
  end
87
100
  end
@@ -1,13 +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
- @@next_id = 0
7
-
8
7
  def initialize block
9
8
  super()
10
9
 
10
+ @age_cutoff = nil
11
11
  header_text 'Aging Work Bar Chart'
12
12
  description_text <<-HTML
13
13
  <p>
@@ -15,16 +15,19 @@ class AgingWorkBarChart < ChartBase
15
15
  newest at the bottom.
16
16
  </p>
17
17
  <p>
18
- There are potentially three bars for each issue, although a bar may be missing if the issue has no
19
- 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.
20
19
  <ol>
21
- <li>The top bar tells you what status the issue is in at any time. The colour indicates the
20
+ <li>Status: The status the issue was in at any time. The colour indicates the
22
21
  status category, which will be one of #{color_block '--status-category-todo-color'} To Do,
23
22
  #{color_block '--status-category-inprogress-color'} In Progress,
24
23
  or #{color_block '--status-category-done-color'} Done</li>
25
- <li>The middle bar indicates #{color_block '--blocked-color'} blocked
24
+ <li>Activity: This bar indicates #{color_block '--blocked-color'} blocked
26
25
  or #{color_block '--stalled-color'} stalled.</li>
27
- <li>The bottom bar indicated #{color_block '--expedited-color'} expedited.</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 %>
28
31
  </ol>
29
32
  </p>
30
33
  #{describe_non_working_days}
@@ -38,6 +41,7 @@ class AgingWorkBarChart < ChartBase
38
41
 
39
42
  def run
40
43
  aging_issues = select_aging_issues issues: @issues
44
+ adjust_time_date_ranges_to_start_from_earliest_issue_start(aging_issues)
41
45
 
42
46
  today = date_range.end
43
47
  sort_by_age! issues: aging_issues, today: today
@@ -53,137 +57,141 @@ class AgingWorkBarChart < ChartBase
53
57
  percentage_line_x = date_range.end - calculate_percent_line if percentage
54
58
 
55
59
  if aging_issues.empty?
56
- @description_text = "<p>There is no aging work</p>"
57
- return render_top_text(binding) #if aging_issues.empty?
60
+ @description_text = '<p>There is no aging work</p>'
61
+ return render_top_text(binding)
58
62
  end
59
63
 
60
64
  wrap_and_render(binding, __FILE__)
61
65
  end
62
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
+
63
77
  def data_sets_for_one_issue issue:, today:
64
78
  cycletime = issue.board.cycletime
65
- issue_start_time = cycletime.started_time(issue)
66
- issue_start_date = issue_start_time.to_date
67
- issue_label = "[#{label_days cycletime.age(issue, today: today)}] #{issue.key}: #{issue.summary}"[0..60]
68
- [
69
- status_data_sets(issue: issue, label: issue_label, today: today),
70
- blocked_data_sets(
71
- issue: issue,
72
- issue_label: issue_label,
73
- stack: 'blocked',
74
- issue_start_time: issue_start_time
75
- ),
76
- data_set_by_block(
77
- issue: issue,
78
- issue_label: issue_label,
79
- title_label: 'Expedited',
80
- stack: 'expedited',
81
- color: CssVariable['--expedited-color'],
82
- start_date: issue_start_date
83
- ) { |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)]
84
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
85
95
  end
86
96
 
87
97
  def sort_by_age! issues:, today:
88
98
  issues.sort! do |a, b|
89
- a.board.cycletime.age(b, today: today) <=> b.board.cycletime.age(a, today: today)
99
+ b.board.cycletime.age(b, today: today) <=> a.board.cycletime.age(a, today: today)
90
100
  end
91
101
  end
92
102
 
93
103
  def select_aging_issues issues:
94
104
  issues.select do |issue|
95
- cycletime = issue.board.cycletime
96
- cycletime.started_time(issue) && cycletime.stopped_time(issue).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)
97
110
  end
98
111
  end
99
112
 
100
113
  def grow_chart_height_if_too_many_issues aging_issue_count:
101
- px_per_bar = 8
114
+ px_per_bar = 10
102
115
  bars_per_issue = 3
116
+ bars_per_issue += 1 if aggregated_project? || current_board.scrum?
117
+
103
118
  preferred_height = aging_issue_count * px_per_bar * bars_per_issue
104
119
  @canvas_height = preferred_height if @canvas_height.nil? || @canvas_height < preferred_height
105
120
  end
106
121
 
107
- def status_data_sets issue:, label:, today:
108
- cycletime = issue.board.cycletime
122
+ def clip_ranges_to_start_time ranges:, issue_start_time:
123
+ return if issue_start_time.nil?
109
124
 
110
- issue_started_time = cycletime.started_time(issue)
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
111
128
 
129
+ def collect_status_ranges issue:, now:
130
+ ranges = []
131
+ issue_started_time = issue.started_stopped_times.first
112
132
  previous_start = nil
113
133
  previous_status = nil
114
-
115
- data_sets = []
116
- issue.changes.each do |change|
117
- next unless change.status?
118
-
119
- status = issue.find_status_by_name change.value
120
-
121
- unless previous_start.nil? || previous_start < issue_started_time
122
- hash = {
123
- type: 'bar',
124
- data: [{
125
- x: [chart_format(previous_start), chart_format(change.time)],
126
- y: label,
127
- title: "#{issue.type} : #{change.value}"
128
- }],
129
- backgroundColor: status_category_color(status),
130
- borderColor: CssVariable['--aging-work-bar-chart-separator-color'],
131
- borderWidth: {
132
- top: 0,
133
- right: 1,
134
- bottom: 0,
135
- left: 0
136
- },
137
- stacked: true,
138
- stack: 'status'
139
- }
140
- 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
141
140
  end
142
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
+ )
143
150
  previous_start = change.time
144
- previous_status = status
151
+ previous_status = new_status
145
152
  end
146
153
 
147
- if previous_start
148
- data_sets << {
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
+ {
149
173
  type: 'bar',
150
174
  data: [{
151
- x: [chart_format(previous_start), chart_format("#{today}T00:00:00#{@timezone_offset}")],
152
- y: label,
153
- title: "#{issue.type} : #{previous_status.name}"
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
154
178
  }],
155
- backgroundColor: status_category_color(previous_status),
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
+ },
156
187
  stacked: true,
157
- stack: 'status'
188
+ stack: stack
158
189
  }
159
190
  end
160
-
161
- data_sets
162
- end
163
-
164
- def one_block_change_data_set starting_change:, ending_time:, issue_label:, stack:, issue_start_time:
165
- deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['blocked_color']
166
- deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['stalled_color']
167
-
168
- color = settings['blocked_color'] || '--blocked-color'
169
- color = settings['stalled_color'] || '--stalled-color' if starting_change.stalled?
170
- {
171
- backgroundColor: CssVariable[color],
172
- data: [
173
- {
174
- title: starting_change.reasons,
175
- x: [chart_format([issue_start_time, starting_change.time].max), chart_format(ending_time)],
176
- y: issue_label
177
- }
178
- ],
179
- stack: stack,
180
- stacked: true,
181
- type: 'bar'
182
- }
183
191
  end
184
192
 
185
- def blocked_data_sets issue:, issue_label:, issue_start_time:, stack:
186
- data_sets = []
193
+ def collect_blocked_stalled_ranges issue:, issue_start_time:
194
+ results = []
187
195
  starting_change = nil
188
196
 
189
197
  issue.blocked_stalled_changes(end_time: time_range.end).each do |change|
@@ -193,58 +201,106 @@ class AgingWorkBarChart < ChartBase
193
201
  end
194
202
 
195
203
  if change.time >= issue_start_time
196
- data_sets << one_block_change_data_set(
197
- starting_change: starting_change, ending_time: change.time,
198
- issue_label: issue_label, stack: stack, issue_start_time: issue_start_time
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
199
209
  )
200
210
  end
201
211
 
202
212
  starting_change = change
203
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 = []
204
222
 
205
- data_sets
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
237
+
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
206
245
  end
207
246
 
208
- def data_set_by_block(
209
- issue:, issue_label:, title_label:, stack:, color:, start_date:, end_date: date_range.end
210
- )
211
- started = nil
212
- ended = nil
213
- data = []
214
-
215
- (start_date..end_date).each do |day|
216
- if yield(day)
217
- started = day if started.nil?
218
- ended = day
219
- elsif ended
220
- data << {
221
- x: [chart_format(started), chart_format(ended)],
222
- y: issue_label,
223
- title: "#{issue.type} : #{title_label} #{label_days (ended - started).to_i + 1}"
224
- }
225
-
226
- started = nil
227
- ended = nil
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 }
228
276
  end
229
277
  end
230
278
 
231
- if started
232
- data << {
233
- x: [chart_format(started), chart_format(ended)],
234
- y: issue_label,
235
- title: "#{issue.type} : #{title_label} #{label_days (end_date - started).to_i + 1}"
236
- }
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
+ )
237
287
  end
238
288
 
239
- return [] if data.empty?
289
+ results
290
+ end
240
291
 
241
- {
242
- type: 'bar',
243
- data: data,
244
- backgroundColor: color,
245
- stacked: true,
246
- stack: stack
247
- }
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
+ )
248
304
  end
249
305
 
250
306
  def calculate_percent_line percentage: 85
@@ -253,4 +309,8 @@ class AgingWorkBarChart < ChartBase
253
309
 
254
310
  days[days.length * percentage / 100]
255
311
  end
312
+
313
+ def age_cutoff days
314
+ @age_cutoff = days
315
+ end
256
316
  end