jirametrics 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +1 -1
  3. data/lib/jirametrics/aging_work_bar_chart.rb +18 -13
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +8 -6
  5. data/lib/jirametrics/aging_work_table.rb +21 -16
  6. data/lib/jirametrics/anonymizer.rb +6 -5
  7. data/lib/jirametrics/chart_base.rb +35 -19
  8. data/lib/jirametrics/css_variable.rb +33 -0
  9. data/lib/jirametrics/cycletime_scatterplot.rb +8 -9
  10. data/lib/jirametrics/daily_wip_by_age_chart.rb +43 -17
  11. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +32 -17
  12. data/lib/jirametrics/daily_wip_by_parent_chart.rb +42 -0
  13. data/lib/jirametrics/daily_wip_chart.rb +67 -6
  14. data/lib/jirametrics/data_quality_report.rb +1 -1
  15. data/lib/jirametrics/dependency_chart.rb +18 -14
  16. data/lib/jirametrics/downloader.rb +13 -11
  17. data/lib/jirametrics/examples/aggregated_project.rb +17 -20
  18. data/lib/jirametrics/examples/standard_project.rb +10 -18
  19. data/lib/jirametrics/expedited_chart.rb +17 -15
  20. data/lib/jirametrics/exporter.rb +26 -20
  21. data/lib/jirametrics/grouping_rules.rb +7 -1
  22. data/lib/jirametrics/html/aging_work_bar_chart.erb +12 -6
  23. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +8 -2
  24. data/lib/jirametrics/html/aging_work_table.erb +11 -19
  25. data/lib/jirametrics/html/cycletime_histogram.erb +9 -3
  26. data/lib/jirametrics/html/cycletime_scatterplot.erb +10 -4
  27. data/lib/jirametrics/html/daily_wip_chart.erb +18 -5
  28. data/lib/jirametrics/html/expedited_chart.erb +11 -5
  29. data/lib/jirametrics/html/hierarchy_table.erb +1 -1
  30. data/lib/jirametrics/html/index.css +186 -0
  31. data/lib/jirametrics/html/index.erb +8 -36
  32. data/lib/jirametrics/html/sprint_burndown.erb +11 -6
  33. data/lib/jirametrics/html/story_point_accuracy_chart.erb +9 -4
  34. data/lib/jirametrics/html/throughput_chart.erb +11 -5
  35. data/lib/jirametrics/html_report_config.rb +28 -3
  36. data/lib/jirametrics/issue.rb +5 -3
  37. data/lib/jirametrics/jira_gateway.rb +5 -2
  38. data/lib/jirametrics/project_config.rb +14 -19
  39. data/lib/jirametrics/settings.json +7 -0
  40. data/lib/jirametrics/sprint_burndown.rb +10 -4
  41. data/lib/jirametrics/status_collection.rb +1 -1
  42. data/lib/jirametrics/story_point_accuracy_chart.rb +20 -10
  43. data/lib/jirametrics/throughput_chart.rb +10 -2
  44. data/lib/jirametrics.rb +2 -0
  45. metadata +7 -3
@@ -13,7 +13,10 @@ class JiraGateway
13
13
 
14
14
  def call_url relative_url:
15
15
  command = make_curl_command url: "#{@jira_url}#{relative_url}"
16
- JSON.parse call_command command
16
+ result = call_command command
17
+ JSON.parse result
18
+ rescue => e # rubocop:disable Style/RescueStandardError
19
+ puts "Error #{e.inspect} when parsing result: #{result.inspect}"
17
20
  end
18
21
 
19
22
  def call_command command
@@ -29,7 +32,7 @@ class JiraGateway
29
32
 
30
33
  def load_jira_config jira_config
31
34
  @jira_url = jira_config['url']
32
- raise "Must specify URL in config" if @jira_url.nil?
35
+ raise 'Must specify URL in config' if @jira_url.nil?
33
36
 
34
37
  @jira_email = jira_config['email']
35
38
  @jira_api_token = jira_config['api_token']
@@ -8,7 +8,7 @@ class ProjectConfig
8
8
 
9
9
  attr_reader :target_path, :jira_config, :all_boards, :possible_statuses,
10
10
  :download_config, :file_configs, :exporter, :data_version, :name, :board_configs,
11
- :settings, :id
11
+ :settings, :aggregate_config
12
12
  attr_accessor :time_range, :jira_url, :id
13
13
 
14
14
  def initialize exporter:, jira_config:, block:, target_path: '.', name: '', id: nil
@@ -22,22 +22,12 @@ class ProjectConfig
22
22
  @name = name
23
23
  @board_configs = []
24
24
  @all_boards = {}
25
- @settings = {
26
- 'stalled_threshold' => 5,
27
- 'blocked_statuses' => [],
28
- 'stalled_statuses' => [],
29
- 'blocked_link_text' => [],
30
-
31
- 'colors' => {
32
- 'stalled' => 'orange',
33
- 'blocked' => '#FF7400'
34
- }
35
- }
25
+ @settings = load_settings
36
26
  @id = id
37
27
  end
38
28
 
39
29
  def evaluate_next_level
40
- instance_eval(&@block)
30
+ instance_eval(&@block) if @block
41
31
  end
42
32
 
43
33
  def run
@@ -58,6 +48,10 @@ class ProjectConfig
58
48
  end
59
49
  end
60
50
 
51
+ def load_settings
52
+ JSON.parse(File.read(File.join(__dir__, 'settings.json')))
53
+ end
54
+
61
55
  def guess_project_id
62
56
  return @id if @id
63
57
 
@@ -121,7 +115,7 @@ class ProjectConfig
121
115
  board_id = $1.to_i
122
116
  load_board board_id: board_id, filename: "#{@target_path}#{file}"
123
117
  end
124
- raise "No boards found in #{@target_path.inspect}" if @all_boards.empty?
118
+ raise "No boards found for #{@file_prefix} in #{@target_path.inspect}" if @all_boards.empty?
125
119
  end
126
120
 
127
121
  def load_board board_id:, filename:
@@ -243,7 +237,7 @@ class ProjectConfig
243
237
 
244
238
  start = json['date_start'] || json['time_start'] # date_start is the current format. Time is the old.
245
239
  stop = json['date_end'] || json['time_end']
246
- @time_range = to_time(start)..to_time(stop)
240
+ @time_range = to_time(start)..to_time(stop, end_of_day: true)
247
241
 
248
242
  @jira_url = json['jira_url']
249
243
  rescue Errno::ENOENT
@@ -251,8 +245,9 @@ class ProjectConfig
251
245
  raise
252
246
  end
253
247
 
254
- def to_time string
255
- string = "#{string}T00:00:00#{@timezone_offset}" if string.match?(/^\d{4}-\d{2}\d{2}$/)
248
+ def to_time string, end_of_day: false
249
+ time = end_of_day ? '23:59:59' : '00:00:00'
250
+ string = "#{string}T#{time}#{@timezone_offset}" if string.match?(/^\d{4}-\d{2}-\d{2}$/)
256
251
  Time.parse string
257
252
  end
258
253
 
@@ -436,8 +431,8 @@ class ProjectConfig
436
431
  else
437
432
  message << "days of data from #{issue.changes.first.time.to_date} to #{cutoff_time.to_date}"
438
433
  end
439
- puts message
434
+ exporter.file_system.log message
440
435
  end
441
- puts "Discarded data from #{issues_cutoff_times.count} issues out of a total #{issues.size}"
436
+ exporter.file_system.log "Discarded data from #{issues_cutoff_times.count} issues out of a total #{issues.size}"
442
437
  end
443
438
  end
@@ -0,0 +1,7 @@
1
+ {
2
+ "stalled_threshold_days": 5,
3
+ "stalled_statuses": [],
4
+
5
+ "blocked_link_text": [],
6
+ "blocked_statuses": []
7
+ }
@@ -22,7 +22,13 @@ class SprintBurndown < ChartBase
22
22
 
23
23
  @summary_stats = {}
24
24
  header_text 'Sprint burndown'
25
- description_text ''
25
+ description_text <<-TEXT
26
+ <div class="p">
27
+ Burndowns for all sprints in this time period. The different colours are only to
28
+ differentiate one sprint from another as they may overlap time periods.
29
+ </div>
30
+ #{describe_non_working_days}
31
+ TEXT
26
32
  end
27
33
 
28
34
  def options= arg
@@ -55,9 +61,9 @@ class SprintBurndown < ChartBase
55
61
  end
56
62
 
57
63
  result = +''
58
- result << '<h1>Sprint Burndowns</h1>'
64
+ result << render_top_text(binding)
59
65
 
60
- possible_colours = %w[blue orange green red brown]
66
+ possible_colours = (1..5).collect { |i| CssVariable["--sprint-burndown-sprint-color-#{i}"] }
61
67
  charts_to_generate = []
62
68
  charts_to_generate << [:data_set_by_story_points, 'Story Points'] if @use_story_points
63
69
  charts_to_generate << [:data_set_by_story_counts, 'Story Count'] if @use_story_counts
@@ -65,7 +71,7 @@ class SprintBurndown < ChartBase
65
71
  @summary_stats.clear
66
72
  data_sets = []
67
73
  sprints.each_with_index do |sprint, index|
68
- color = possible_colours[index % 5]
74
+ color = possible_colours[index % possible_colours.size]
69
75
  label = sprint.name
70
76
  data = send(data_method, sprint: sprint, change_data_for_sprint: change_data_by_sprint[sprint])
71
77
  data_sets << {
@@ -32,7 +32,7 @@ class StatusCollection
32
32
  next
33
33
  else
34
34
  all_status_names = @list.collect { |s| "#{s.name.inspect}:#{s.id.inspect}" }.uniq.sort.join(', ')
35
- raise "Status not found: #{name_or_id}. Possible statuses are: #{all_status_names}"
35
+ raise "Status not found: \"#{name_or_id}\". Possible statuses are: #{all_status_names}"
36
36
  end
37
37
  end
38
38
 
@@ -6,15 +6,20 @@ class StoryPointAccuracyChart < ChartBase
6
6
 
7
7
  header_text 'Estimate Accuracy'
8
8
  description_text <<-HTML
9
- <p>
9
+ <div class="p">
10
10
  This chart graphs estimates against actual recorded cycle times. Since
11
11
  estimates can change over time, we're graphing the estimate at the time that the story started.
12
- </p>
13
- <p>
14
- The completed dots indicate cycletimes. The aging dots (if you turn them on) show the current
15
- age of items, which will give you a hint as to where they might end up. If they're already
16
- far to the right then you know you have a problem.
17
- </p>
12
+ </div>
13
+ <div class="p">
14
+ The #{color_block '--estimate-accuracy-chart-completed-fill-color'} completed dots indicate
15
+ cycletimes.
16
+ <% if @has_aging_data %>
17
+ The #{color_block '--estimate-accuracy-chart-active-fill-color'} aging dots
18
+ (click on the legend to turn them on) show the current
19
+ age of items, which will give you a hint as to where they might end up. If they're already
20
+ far to the right then you know you have a problem.
21
+ <% end %>
22
+ </div>
18
23
  HTML
19
24
 
20
25
  @y_axis_label = 'Story Point Estimates'
@@ -55,10 +60,15 @@ class StoryPointAccuracyChart < ChartBase
55
60
  (hash[key] ||= []) << issue
56
61
  end
57
62
 
63
+ @has_aging_data = !aging_hash.empty?
64
+
58
65
  [
59
- [completed_hash, 'Completed', '#66FF99', 'green', false],
60
- [aging_hash, 'Still in progress', '#FFCCCB', 'red', true]
61
- ].filter_map do |hash, label, fill_color, border_color, starts_hidden|
66
+ [completed_hash, 'Completed', 'completed', false],
67
+ [aging_hash, 'Still in progress', 'active', true]
68
+ ].filter_map do |hash, label, completed_or_active, starts_hidden|
69
+ fill_color = CssVariable["--estimate-accuracy-chart-#{completed_or_active}-fill-color"]
70
+ border_color = CssVariable["--estimate-accuracy-chart-#{completed_or_active}-border-color"]
71
+
62
72
  # We sort so that the smaller circles are in front of the bigger circles.
63
73
  data = hash.sort(&hash_sorter).collect do |key, values|
64
74
  estimate, cycle_time = *key
@@ -9,7 +9,12 @@ class ThroughputChart < ChartBase
9
9
  super()
10
10
 
11
11
  header_text 'Throughput Chart'
12
- description_text 'This chart shows how many items we completed per unit of time'
12
+ description_text <<-TEXT
13
+ <div class="p">
14
+ This chart shows how many items we completed per week
15
+ </div>
16
+ #{describe_non_working_days}
17
+ TEXT
13
18
 
14
19
  init_configuration_block(block) do
15
20
  grouping_rules do |issue, rule|
@@ -25,7 +30,10 @@ class ThroughputChart < ChartBase
25
30
  data_sets = []
26
31
  if rules_to_issues.size > 1
27
32
  data_sets << weekly_throughput_dataset(
28
- completed_issues: completed_issues, label: 'Totals', color: 'gray', dashed: true
33
+ completed_issues: completed_issues,
34
+ label: 'Totals',
35
+ color: CssVariable['--throughput_chart_total_line_color'],
36
+ dashed: true
29
37
  )
30
38
  end
31
39
 
data/lib/jirametrics.rb CHANGED
@@ -51,6 +51,7 @@ class JiraMetrics < Thor
51
51
  require 'jirametrics/daily_wip_chart'
52
52
  require 'jirametrics/groupable_issue_chart'
53
53
  require 'jirametrics/discard_changes_before'
54
+ require 'jirametrics/css_variable'
54
55
 
55
56
  require 'jirametrics/aggregate_config'
56
57
  require 'jirametrics/expedited_chart'
@@ -65,6 +66,7 @@ class JiraMetrics < Thor
65
66
  require 'jirametrics/sprint'
66
67
  require 'jirametrics/issue'
67
68
  require 'jirametrics/daily_wip_by_age_chart'
69
+ require 'jirametrics/daily_wip_by_parent_chart'
68
70
  require 'jirametrics/aging_work_in_progress_chart'
69
71
  require 'jirametrics/cycletime_scatterplot'
70
72
  require 'jirametrics/sprint_issue_change_data'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirametrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-23 00:00:00.000000000 Z
11
+ date: 2024-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: random-word
@@ -74,11 +74,13 @@ files:
74
74
  - lib/jirametrics/change_item.rb
75
75
  - lib/jirametrics/chart_base.rb
76
76
  - lib/jirametrics/columns_config.rb
77
+ - lib/jirametrics/css_variable.rb
77
78
  - lib/jirametrics/cycletime_config.rb
78
79
  - lib/jirametrics/cycletime_histogram.rb
79
80
  - lib/jirametrics/cycletime_scatterplot.rb
80
81
  - lib/jirametrics/daily_wip_by_age_chart.rb
81
82
  - lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb
83
+ - lib/jirametrics/daily_wip_by_parent_chart.rb
82
84
  - lib/jirametrics/daily_wip_chart.rb
83
85
  - lib/jirametrics/data_quality_report.rb
84
86
  - lib/jirametrics/dependency_chart.rb
@@ -107,6 +109,7 @@ files:
107
109
  - lib/jirametrics/html/data_quality_report.erb
108
110
  - lib/jirametrics/html/expedited_chart.erb
109
111
  - lib/jirametrics/html/hierarchy_table.erb
112
+ - lib/jirametrics/html/index.css
110
113
  - lib/jirametrics/html/index.erb
111
114
  - lib/jirametrics/html/sprint_burndown.erb
112
115
  - lib/jirametrics/html/story_point_accuracy_chart.erb
@@ -118,6 +121,7 @@ files:
118
121
  - lib/jirametrics/project_config.rb
119
122
  - lib/jirametrics/rules.rb
120
123
  - lib/jirametrics/self_or_issue_dispatcher.rb
124
+ - lib/jirametrics/settings.json
121
125
  - lib/jirametrics/sprint.rb
122
126
  - lib/jirametrics/sprint_burndown.rb
123
127
  - lib/jirametrics/sprint_issue_change_data.rb
@@ -151,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
155
  - !ruby/object:Gem::Version
152
156
  version: '0'
153
157
  requirements: []
154
- rubygems_version: 3.5.7
158
+ rubygems_version: 3.5.10
155
159
  signing_key:
156
160
  specification_version: 4
157
161
  summary: Extract Jira metrics