jirametrics 2.20 → 2.22

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aging_work_bar_chart.rb +176 -134
  3. data/lib/jirametrics/bar_chart_range.rb +17 -0
  4. data/lib/jirametrics/board.rb +4 -0
  5. data/lib/jirametrics/board_config.rb +2 -1
  6. data/lib/jirametrics/change_item.rb +10 -3
  7. data/lib/jirametrics/chart_base.rb +31 -0
  8. data/lib/jirametrics/cycletime_config.rb +4 -5
  9. data/lib/jirametrics/cycletime_scatterplot.rb +36 -17
  10. data/lib/jirametrics/daily_wip_by_age_chart.rb +3 -4
  11. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +13 -3
  12. data/lib/jirametrics/daily_wip_chart.rb +1 -1
  13. data/lib/jirametrics/data_quality_report.rb +2 -0
  14. data/lib/jirametrics/exporter.rb +4 -2
  15. data/lib/jirametrics/fix_version.rb +13 -0
  16. data/lib/jirametrics/groupable_issue_chart.rb +7 -1
  17. data/lib/jirametrics/html/aging_work_bar_chart.erb +2 -1
  18. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +2 -0
  19. data/lib/jirametrics/html/aging_work_table.erb +2 -0
  20. data/lib/jirametrics/html/cycletime_histogram.erb +2 -0
  21. data/lib/jirametrics/html/cycletime_scatterplot.erb +6 -6
  22. data/lib/jirametrics/html/daily_wip_chart.erb +2 -0
  23. data/lib/jirametrics/html/estimate_accuracy_chart.erb +2 -0
  24. data/lib/jirametrics/html/expedited_chart.erb +3 -1
  25. data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +2 -0
  26. data/lib/jirametrics/html/index.css +17 -0
  27. data/lib/jirametrics/html/index.erb +1 -1
  28. data/lib/jirametrics/html/index.js +24 -0
  29. data/lib/jirametrics/html/sprint_burndown.erb +6 -0
  30. data/lib/jirametrics/html/throughput_chart.erb +2 -2
  31. data/lib/jirametrics/html_generator.rb +31 -0
  32. data/lib/jirametrics/html_report_config.rb +5 -24
  33. data/lib/jirametrics/issue.rb +97 -4
  34. data/lib/jirametrics/jira_gateway.rb +1 -1
  35. data/lib/jirametrics/project_config.rb +12 -2
  36. data/lib/jirametrics/raw_javascript.rb +13 -0
  37. data/lib/jirametrics/sprint.rb +12 -0
  38. data/lib/jirametrics/sprint_burndown.rb +6 -2
  39. data/lib/jirametrics/stitcher.rb +75 -0
  40. data/lib/jirametrics.rb +8 -1
  41. metadata +5 -1
@@ -58,7 +58,16 @@ class ProjectConfig
58
58
 
59
59
  def load_settings
60
60
  # This is the weird exception that we don't ever want mocked out so we skip FileSystem entirely.
61
- JSON.parse(File.read(File.join(__dir__, 'settings.json'), encoding: 'UTF-8'))
61
+ settings = JSON.parse(File.read(File.join(__dir__, 'settings.json'), encoding: 'UTF-8'))
62
+
63
+ if settings['blocked_color']
64
+ file_system.deprecated message: 'blocked color should be set via css now', date: '2024-05-03'
65
+ end
66
+ if settings['stalled_color']
67
+ file_system.deprecated message: 'stalled color should be set via css now', date: '2024-05-03'
68
+ end
69
+
70
+ settings
62
71
  end
63
72
 
64
73
  def guess_project_id
@@ -295,8 +304,9 @@ class ProjectConfig
295
304
  file_system.foreach(@target_path) do |file|
296
305
  next unless file =~ /^#{get_file_prefix}_board_(\d+)_sprints_\d+.json$/
297
306
 
307
+ board_id = $1.to_i
298
308
  file_path = File.join(@target_path, file)
299
- board = @all_boards[$1.to_i]
309
+ board = @all_boards[board_id]
300
310
  unless board
301
311
  @exporter.file_system.log(
302
312
  'Found sprint data but can\'t find a matching board in config. ' \
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # When strings are serialized into JSON, they're converted to actual strings. The purpose
4
+ # of this class is to allow raw javascript to be passed through.
5
+ class RawJavascript
6
+ def initialize content
7
+ @content = content
8
+ end
9
+
10
+ def to_json(*_args)
11
+ @content
12
+ end
13
+ end
@@ -13,6 +13,7 @@ class Sprint
13
13
  def id = @raw['id']
14
14
  def active? = (@raw['state'] == 'active')
15
15
  def closed? = (@raw['state'] == 'closed')
16
+ def future? = (@raw['state'] == 'future')
16
17
 
17
18
  def completed_at? time
18
19
  completed_at = completed_time
@@ -36,6 +37,17 @@ class Sprint
36
37
  def goal = @raw['goal']
37
38
  def name = @raw['name']
38
39
 
40
+ def day_count
41
+ return '' if future?
42
+
43
+ if closed?
44
+ days = (completed_time.to_date - start_time.to_date).to_i + 1
45
+ else
46
+ days = (end_time.to_date - start_time.to_date).to_i + 1
47
+ end
48
+ "#{days} days"
49
+ end
50
+
39
51
  private
40
52
 
41
53
  def parse_time time_string
@@ -48,8 +48,9 @@ class SprintBurndown < ChartBase
48
48
  end
49
49
 
50
50
  def run
51
- sprints = sprints_in_time_range all_boards[board_id]
52
- return nil if sprints.empty?
51
+ return nil unless current_board.scrum?
52
+
53
+ sprints = sprints_in_time_range current_board
53
54
 
54
55
  change_data_by_sprint = {}
55
56
  sprints.each do |sprint|
@@ -110,6 +111,9 @@ class SprintBurndown < ChartBase
110
111
 
111
112
  def sprints_in_time_range board
112
113
  board.sprints.select do |sprint|
114
+ # If it's never been started then it's just a holding area. Ignore it.
115
+ next if sprint.future?
116
+
113
117
  sprint_end_time = sprint.completed_time || sprint.end_time
114
118
  sprint_start_time = sprint.start_time
115
119
  next false if sprint_start_time.nil?
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Stitcher < HtmlGenerator
4
+ class StitchContent
5
+ include ValueEquality
6
+
7
+ attr_reader :file, :title, :content, :type
8
+
9
+ def initialize file:, title:, type:, content:
10
+ @file = file
11
+ @title = title
12
+ @content = content
13
+ @type = type
14
+ end
15
+ end
16
+
17
+ attr_reader :loaded_files, :all_stitches
18
+
19
+ def initialize file_system:
20
+ super()
21
+ self.file_system = file_system
22
+ @all_stitches = []
23
+ @loaded_files = []
24
+ end
25
+
26
+ def run stitch_file:
27
+ output_filename = make_output_filename stitch_file
28
+ file_system.log "Creating file #{output_filename.inspect}", also_write_to_stderr: true
29
+ erb = ERB.new file_system.load(stitch_file)
30
+ @sections = [[erb.result(binding), :body]]
31
+ create_html output_filename: output_filename, settings: {}
32
+ end
33
+
34
+ def make_output_filename input_filename
35
+ if /^(.+)\.erb$/ =~ input_filename
36
+ "#{$1}.html"
37
+ else
38
+ "#{input_filename}.html"
39
+ end
40
+ end
41
+
42
+ def grab_by_title title, from_file:, type: 'chart'
43
+ parse_file from_file
44
+ stitch_content = @all_stitches.find { |s| s.file == from_file && s.title == title && s.type == type }
45
+ return stitch_content.content if stitch_content
46
+
47
+ raise "Unable to find content in file #{from_file.inspect} matching title: #{title.inspect}"
48
+ end
49
+
50
+ def parse_file filename
51
+ return false if @loaded_files.include? filename
52
+
53
+ # To match: <!-- seam-start | chart78 | GithubPrScatterplot | PR Scatterplot | chart -->
54
+ regex = /^<!-- seam-(?<seam>start|end) \| (?<id>[^|]+) \| (?<clazz>[^|]+) \| (?<title>[^|]+) \| (?<type>[^|]+) -->$/
55
+ content = nil
56
+ file_system.load(filename).lines do |line|
57
+ matches = line.match(regex)
58
+ if matches
59
+ if matches[:seam] == 'start'
60
+ content = +''
61
+ else
62
+ @all_stitches << Stitcher::StitchContent.new(
63
+ file: filename, title: matches[:title], type: matches[:type], content: content
64
+ )
65
+ content = nil
66
+ end
67
+ elsif content
68
+ content << line
69
+ end
70
+ end
71
+
72
+ @loaded_files << filename
73
+ true
74
+ end
75
+ end
data/lib/jirametrics.rb CHANGED
@@ -52,11 +52,18 @@ class JiraMetrics < Thor
52
52
  Exporter.instance.info(key, name_filter: options[:name] || '*')
53
53
  end
54
54
 
55
+ option :config
56
+ desc 'stitch', 'Dump information about one issue'
57
+ def stitch stitch_file = 'stitcher.erb'
58
+ load_config options[:config]
59
+ Exporter.instance.stitch stitch_file
60
+ end
61
+
55
62
  no_commands do
56
63
  def load_config config_file, file_system: FileSystem.new
57
64
  config_file = './config.rb' if config_file.nil?
58
65
 
59
- if File.exist? config_file
66
+ if file_system.file_exist? config_file
60
67
  # The fact that File.exist can see the file does not mean that require will be
61
68
  # able to load it. Convert this to an absolute pathname now for require.
62
69
  config_file = File.absolute_path(config_file).to_s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirametrics
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.20'
4
+ version: '2.22'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler
@@ -66,6 +66,7 @@ files:
66
66
  - lib/jirametrics/aging_work_table.rb
67
67
  - lib/jirametrics/anonymizer.rb
68
68
  - lib/jirametrics/atlassian_document_format.rb
69
+ - lib/jirametrics/bar_chart_range.rb
69
70
  - lib/jirametrics/blocked_stalled_change.rb
70
71
  - lib/jirametrics/board.rb
71
72
  - lib/jirametrics/board_column.rb
@@ -118,12 +119,14 @@ files:
118
119
  - lib/jirametrics/html/index.js
119
120
  - lib/jirametrics/html/sprint_burndown.erb
120
121
  - lib/jirametrics/html/throughput_chart.erb
122
+ - lib/jirametrics/html_generator.rb
121
123
  - lib/jirametrics/html_report_config.rb
122
124
  - lib/jirametrics/issue.rb
123
125
  - lib/jirametrics/issue_collection.rb
124
126
  - lib/jirametrics/issue_link.rb
125
127
  - lib/jirametrics/jira_gateway.rb
126
128
  - lib/jirametrics/project_config.rb
129
+ - lib/jirametrics/raw_javascript.rb
127
130
  - lib/jirametrics/rules.rb
128
131
  - lib/jirametrics/self_or_issue_dispatcher.rb
129
132
  - lib/jirametrics/settings.json
@@ -132,6 +135,7 @@ files:
132
135
  - lib/jirametrics/sprint_issue_change_data.rb
133
136
  - lib/jirametrics/status.rb
134
137
  - lib/jirametrics/status_collection.rb
138
+ - lib/jirametrics/stitcher.rb
135
139
  - lib/jirametrics/throughput_chart.rb
136
140
  - lib/jirametrics/tree_organizer.rb
137
141
  - lib/jirametrics/trend_line_calculator.rb