jirametrics 2.31 → 3.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +42 -29
  3. data/lib/jirametrics/aging_work_bar_chart.rb +52 -40
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +65 -60
  5. data/lib/jirametrics/aging_work_table.rb +36 -33
  6. data/lib/jirametrics/anonymizer.rb +31 -86
  7. data/lib/jirametrics/atlassian_document_format.rb +11 -4
  8. data/lib/jirametrics/blocked_stalled_by_date_builder.rb +64 -0
  9. data/lib/jirametrics/blocked_stalled_change.rb +5 -1
  10. data/lib/jirametrics/blocked_stalled_change_stream_builder.rb +194 -0
  11. data/lib/jirametrics/board.rb +23 -9
  12. data/lib/jirametrics/board_config.rb +0 -7
  13. data/lib/jirametrics/board_movement_calculator.rb +24 -21
  14. data/lib/jirametrics/cfd_data_builder.rb +46 -44
  15. data/lib/jirametrics/change_item.rb +19 -7
  16. data/lib/jirametrics/chart_base.rb +50 -64
  17. data/lib/jirametrics/cumulative_flow_diagram.rb +59 -46
  18. data/lib/jirametrics/cycle_time_config.rb +35 -46
  19. data/lib/jirametrics/cycletime_histogram.rb +7 -0
  20. data/lib/jirametrics/cycletime_scatterplot.rb +7 -0
  21. data/lib/jirametrics/daily_view.rb +83 -61
  22. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +24 -9
  23. data/lib/jirametrics/daily_wip_chart.rb +72 -45
  24. data/lib/jirametrics/data_quality_report.rb +61 -63
  25. data/lib/jirametrics/dependency_chart.rb +40 -31
  26. data/lib/jirametrics/download_config.rb +0 -3
  27. data/lib/jirametrics/downloader.rb +8 -7
  28. data/lib/jirametrics/downloader_for_cloud.rb +108 -72
  29. data/lib/jirametrics/estimate_accuracy_chart.rb +13 -13
  30. data/lib/jirametrics/examples/standard_project.rb +2 -2
  31. data/lib/jirametrics/expedited_chart.rb +47 -37
  32. data/lib/jirametrics/exporter.rb +19 -10
  33. data/lib/jirametrics/file_config.rb +19 -12
  34. data/lib/jirametrics/file_system.rb +16 -10
  35. data/lib/jirametrics/flow_efficiency_calculator.rb +62 -0
  36. data/lib/jirametrics/flow_efficiency_scatterplot.rb +4 -2
  37. data/lib/jirametrics/github_gateway.rb +23 -9
  38. data/lib/jirametrics/groupable_issue_chart.rb +3 -0
  39. data/lib/jirametrics/html/index.css +6 -0
  40. data/lib/jirametrics/html_report_config.rb +9 -29
  41. data/lib/jirametrics/issue.rb +290 -389
  42. data/lib/jirametrics/issue_collection.rb +1 -0
  43. data/lib/jirametrics/issue_printer.rb +60 -23
  44. data/lib/jirametrics/jira_gateway.rb +29 -18
  45. data/lib/jirametrics/mcp_server.rb +225 -209
  46. data/lib/jirametrics/project_config.rb +158 -134
  47. data/lib/jirametrics/pull_request_cycle_time_histogram.rb +1 -20
  48. data/lib/jirametrics/pull_request_cycle_time_scatterplot.rb +11 -33
  49. data/lib/jirametrics/rules.rb +1 -0
  50. data/lib/jirametrics/self_or_issue_dispatcher.rb +1 -3
  51. data/lib/jirametrics/sprint_burndown.rb +142 -242
  52. data/lib/jirametrics/sprint_count_measure.rb +42 -0
  53. data/lib/jirametrics/sprint_issue_change_data.rb +1 -0
  54. data/lib/jirametrics/sprint_points_measure.rb +62 -0
  55. data/lib/jirametrics/sprint_summary_stats.rb +16 -0
  56. data/lib/jirametrics/status_collection.rb +2 -2
  57. data/lib/jirametrics/stitcher.rb +21 -16
  58. data/lib/jirametrics/throughput_chart.rb +38 -24
  59. data/lib/jirametrics/time_based_chart.rb +65 -0
  60. data/lib/jirametrics/time_based_histogram.rb +30 -23
  61. data/lib/jirametrics/time_based_scatterplot.rb +11 -2
  62. data/lib/jirametrics/trend_line_calculator.rb +2 -2
  63. data/lib/jirametrics/wip_by_column_chart.rb +73 -32
  64. data/lib/jirametrics.rb +6 -2
  65. metadata +13 -20
@@ -7,7 +7,8 @@ class FlowEfficiencyScatterplot < ChartBase
7
7
 
8
8
  attr_accessor :possible_statuses
9
9
 
10
- def initialize block
10
+ # Long only because of the inline description_text heredoc and one-time setup; splitting wouldn't help.
11
+ def initialize block # rubocop:disable Metrics/MethodLength
11
12
  super()
12
13
 
13
14
  header_text 'Flow Efficiency'
@@ -63,7 +64,8 @@ class FlowEfficiencyScatterplot < ChartBase
63
64
  end
64
65
 
65
66
  if data_sets.empty?
66
- return "<h1 class='foldable'>#{@header_text}</h1><div>No data matched the selected criteria. Nothing to show.</div>"
67
+ return "<h1 class='foldable'>#{@header_text}</h1>" \
68
+ '<div>No data matched the selected criteria. Nothing to show.</div>'
67
69
  end
68
70
 
69
71
  wrap_and_render(binding, __FILE__)
@@ -139,14 +139,18 @@ class GithubGateway
139
139
  # fallback fetches the complete set rather than us caching a partial answer.
140
140
  next if commits['totalCount'] && commits['totalCount'] > nodes.size
141
141
 
142
- messages_by_number[number] = nodes.flat_map do |node|
143
- commit = node['commit'] || {}
144
- [commit['messageHeadline'], commit['messageBody']].compact
145
- end
142
+ messages_by_number[number] = commit_messages_from(nodes)
146
143
  end
147
144
  messages_by_number
148
145
  end
149
146
 
147
+ def commit_messages_from nodes
148
+ nodes.flat_map do |node|
149
+ commit = node['commit'] || {}
150
+ [commit['messageHeadline'], commit['messageBody']].compact
151
+ end
152
+ end
153
+
150
154
  def owner_and_name
151
155
  # @repo may be a full URL (https://github.com/owner/name.git) or an owner/name slug.
152
156
  @repo.sub(%r{\Ahttps?://[^/]+/}, '').delete_suffix('.git').split('/', 2)
@@ -195,6 +199,11 @@ class GithubGateway
195
199
  unless status.success?
196
200
  error_message = " GitHub CLI command failed for #{@repo} " \
197
201
  "(attempt #{attempts}/#{MAX_RETRIES}): #{stderr.strip}"
202
+ # stderr is a String (from Open3.capture3), so this include? is String#include? — a substring
203
+ # match ("does the error text contain this phrase?"), not Array membership. Style/ArrayIntersect
204
+ # can't tell the two apart; its `.intersect?(stderr)` rewrite would be an exact-equality check
205
+ # that never matches, silently killing the retry-on-transient-error logic.
206
+ # rubocop:disable Style/ArrayIntersect
198
207
  if attempts < MAX_RETRIES && TRANSIENT_ERROR_PATTERNS.any? { |pattern| stderr.include?(pattern) }
199
208
  delay = 2**attempts
200
209
  @file_system.log error_message
@@ -202,15 +211,20 @@ class GithubGateway
202
211
  sleep delay
203
212
  next
204
213
  end
214
+ # rubocop:enable Style/ArrayIntersect
205
215
  @file_system.warning error_message
206
216
  raise "GitHub CLI command failed for #{@repo}: #{stderr}"
207
217
  end
208
218
 
209
- result = JSON.parse(stdout)
210
- if result.nil? || (result.is_a?(Array) && result.empty?)
211
- @file_system.warning "No data was found in GitHub for #{@repo}. Is that what you expected?"
212
- end
213
- return result
219
+ return parse_command_result(stdout)
220
+ end
221
+ end
222
+
223
+ def parse_command_result stdout
224
+ result = JSON.parse(stdout)
225
+ if result.nil? || (result.is_a?(Array) && result.empty?)
226
+ @file_system.warning "No data was found in GitHub for #{@repo}. Is that what you expected?"
214
227
  end
228
+ result
215
229
  end
216
230
  end
@@ -4,6 +4,9 @@ require 'jirametrics/rules'
4
4
  require 'jirametrics/grouping_rules'
5
5
 
6
6
  module GroupableIssueChart
7
+ attr_accessor :issue_hints, :issue_periods
8
+ attr_reader :group_by_block
9
+
7
10
  def init_configuration_block user_provided_block, &default_block
8
11
  instance_eval(&user_provided_block)
9
12
  instance_eval(&default_block) unless @group_by_block
@@ -74,6 +74,10 @@
74
74
  --sprint-burndown-sprint-color-6: #56B4E9; /* Okabe-Ito sky blue */
75
75
  --sprint-burndown-sprint-color-7: #F0E442; /* Okabe-Ito yellow */
76
76
 
77
+ --cfd-triangle-color: #333333; /* dark triangle on light background */
78
+ --cfd-arrival-rate-line-color: rgba(255,138,101,0.85); /* orange */
79
+ --cfd-departure-rate-line-color: rgba(128,203,196,0.85); /* teal */
80
+
77
81
  --sprint-color: #56B4E9; /* Okabe-Ito sky blue */
78
82
 
79
83
  --daily-view-selected-issue-background: lightgray;
@@ -253,6 +257,7 @@ html[data-theme="dark"] {
253
257
  --expedited-chart-dot-expedite-started-color: #E69F00;
254
258
  --expedited-chart-dot-expedite-stopped-color: #2DCB9A;
255
259
  --sprint-burndown-sprint-color-1: #56B4E9; /* sky blue */
260
+ --cfd-triangle-color: #ffffff; /* light triangle on dark background */
256
261
  --sprint-burndown-sprint-color-3: #2DCB9A; /* lighter bluish green */
257
262
  --sprint-burndown-sprint-color-4: #CC79A7; /* reddish purple (vermilion → orange conflicts with color-2) */
258
263
  --sprint-burndown-sprint-color-5: #F0E442; /* yellow */
@@ -407,6 +412,7 @@ html[data-theme="light"] {
407
412
  --expedited-chart-dot-expedite-stopped-color: #2DCB9A;
408
413
 
409
414
  --sprint-burndown-sprint-color-1: #56B4E9; /* sky blue */
415
+ --cfd-triangle-color: #ffffff; /* light triangle on dark background */
410
416
  --sprint-burndown-sprint-color-3: #2DCB9A; /* lighter bluish green */
411
417
  --sprint-burndown-sprint-color-4: #CC79A7; /* reddish purple (vermilion → orange conflicts with color-2) */
412
418
  --sprint-burndown-sprint-color-5: #F0E442; /* yellow */
@@ -8,23 +8,6 @@ class HtmlReportConfig < HtmlGenerator
8
8
 
9
9
  attr_reader :file_config, :sections, :charts
10
10
 
11
- def self.define_chart name:, classname:, deprecated_warning: nil, deprecated_date: nil
12
- lines = []
13
- lines << "def #{name} &block"
14
- lines << ' block = ->(_) {} unless block'
15
- if deprecated_warning
16
- lines << " file_system.deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
17
- end
18
- lines << " execute_chart #{classname}.new(block)"
19
- lines << 'end'
20
- module_eval lines.join("\n"), __FILE__, __LINE__
21
- end
22
-
23
- define_chart name: 'daily_wip_by_type', classname: 'DailyWipChart',
24
- deprecated_warning: 'This is the same as daily_wip_chart. Please use that one', deprecated_date: '2024-05-23'
25
- define_chart name: 'story_point_accuracy_chart', classname: 'EstimateAccuracyChart',
26
- deprecated_warning: 'Renamed to estimate_accuracy_chart. Please use that one', deprecated_date: '2024-05-23'
27
-
28
11
  def initialize file_config:, block:
29
12
  super()
30
13
  @file_config = file_config
@@ -55,10 +38,7 @@ class HtmlReportConfig < HtmlGenerator
55
38
  end
56
39
 
57
40
  def execute_chart_per_board klass:, block:, board_id:
58
- all_boards = @file_config.project_config.all_boards
59
- ids = board_id ? [board_id] : issues.collect { |i| i.board.id }.uniq
60
- ids = ids.sort_by { |id| all_boards[id]&.name || '' }
61
- ids.each_with_index do |id, index|
41
+ board_ids(board_id).each_with_index do |id, index|
62
42
  execute_chart(klass.new(block)) do |chart|
63
43
  chart.board_id = id
64
44
  # We're showing the description only on the first one in order to reduce noise on the report
@@ -67,6 +47,14 @@ class HtmlReportConfig < HtmlGenerator
67
47
  end
68
48
  end
69
49
 
50
+ # The boards to draw this chart for, sorted by name: just the requested one, or every board the
51
+ # issues touch.
52
+ def board_ids board_id
53
+ all_boards = @file_config.project_config.all_boards
54
+ ids = board_id ? [board_id] : issues.collect { |issue| issue.board.id }.uniq
55
+ ids.sort_by { |id| all_boards[id]&.name || '' }
56
+ end
57
+
70
58
  def respond_to_missing? name, include_private = false
71
59
  class_name = name.to_s.split('_').map(&:capitalize).join
72
60
  !resolve_chart_class(class_name).nil? || super
@@ -200,12 +188,4 @@ class HtmlReportConfig < HtmlGenerator
200
188
  </section>
201
189
  HTML
202
190
  end
203
-
204
- def discard_changes_before status_becomes: nil, &block
205
- file_system.deprecated(
206
- date: '2025-01-09',
207
- message: 'discard_changes_before is now only supported at the project level'
208
- )
209
- file_config.project_config.discard_changes_before status_becomes: status_becomes, &block
210
- end
211
191
  end