jirametrics 2.6 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +6 -1
  3. data/lib/jirametrics/aging_work_bar_chart.rb +6 -6
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +1 -1
  5. data/lib/jirametrics/aging_work_table.rb +4 -5
  6. data/lib/jirametrics/blocked_stalled_change.rb +1 -1
  7. data/lib/jirametrics/board.rb +14 -12
  8. data/lib/jirametrics/chart_base.rb +16 -10
  9. data/lib/jirametrics/cycletime_config.rb +26 -7
  10. data/lib/jirametrics/cycletime_histogram.rb +1 -1
  11. data/lib/jirametrics/cycletime_scatterplot.rb +3 -6
  12. data/lib/jirametrics/daily_wip_by_age_chart.rb +2 -4
  13. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +2 -2
  14. data/lib/jirametrics/daily_wip_by_parent_chart.rb +0 -4
  15. data/lib/jirametrics/daily_wip_chart.rb +7 -9
  16. data/lib/jirametrics/data_quality_report.rb +166 -7
  17. data/lib/jirametrics/dependency_chart.rb +3 -4
  18. data/lib/jirametrics/discard_changes_before.rb +1 -1
  19. data/lib/jirametrics/downloader.rb +14 -13
  20. data/lib/jirametrics/estimate_accuracy_chart.rb +1 -2
  21. data/lib/jirametrics/examples/aggregated_project.rb +1 -3
  22. data/lib/jirametrics/examples/standard_project.rb +10 -9
  23. data/lib/jirametrics/expedited_chart.rb +1 -2
  24. data/lib/jirametrics/exporter.rb +25 -0
  25. data/lib/jirametrics/file_system.rb +1 -1
  26. data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
  27. data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
  28. data/lib/jirametrics/html/index.css +10 -3
  29. data/lib/jirametrics/html_report_config.rb +2 -1
  30. data/lib/jirametrics/issue.rb +63 -11
  31. data/lib/jirametrics/jira_gateway.rb +1 -1
  32. data/lib/jirametrics/project_config.rb +27 -19
  33. data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
  34. data/lib/jirametrics/sprint_burndown.rb +2 -2
  35. data/lib/jirametrics/status.rb +1 -1
  36. data/lib/jirametrics/status_collection.rb +4 -0
  37. data/lib/jirametrics/throughput_chart.rb +1 -1
  38. data/lib/jirametrics.rb +15 -5
  39. metadata +8 -7
  40. data/lib/jirametrics/html/data_quality_report.erb +0 -126
@@ -24,22 +24,30 @@ class ProjectConfig
24
24
  @all_boards = {}
25
25
  @settings = load_settings
26
26
  @id = id
27
+ @has_loaded_data = false
27
28
  end
28
29
 
29
30
  def evaluate_next_level
30
31
  instance_eval(&@block) if @block
31
32
  end
32
33
 
33
- def run
34
- unless aggregated_project?
35
- load_all_boards
36
- @id = guess_project_id
37
- load_status_category_mappings
38
- load_project_metadata
39
- load_sprints
40
- end
34
+ def load_data
35
+ return if @has_loaded_data
36
+
37
+ @has_loaded_data = true
38
+ load_all_boards
39
+ @id = guess_project_id
40
+ load_status_category_mappings
41
+ load_project_metadata
42
+ load_sprints
43
+ end
44
+
45
+ def run load_only: false
46
+ load_data unless aggregated_project?
41
47
  anonymize_data if @anonymizer_needed
42
48
 
49
+ return if load_only
50
+
43
51
  @board_configs.each do |board_config|
44
52
  board_config.run
45
53
  end
@@ -117,7 +125,7 @@ class ProjectConfig
117
125
  board_id = $1.to_i
118
126
  load_board board_id: board_id, filename: "#{@target_path}#{file}"
119
127
  end
120
- raise "No boards found for #{@file_prefix} in #{@target_path.inspect}" if @all_boards.empty?
128
+ raise "No boards found for #{@file_prefix.inspect} in #{@target_path.inspect}" if @all_boards.empty?
121
129
  end
122
130
 
123
131
  def load_board board_id:, filename:
@@ -232,7 +240,7 @@ class ProjectConfig
232
240
  end
233
241
 
234
242
  def load_project_metadata
235
- filename = "#{@target_path}/#{file_prefix}_meta.json"
243
+ filename = File.join @target_path, "#{file_prefix}_meta.json"
236
244
  json = JSON.parse(file_system.load(filename))
237
245
 
238
246
  @data_version = json['version'] || 1
@@ -243,7 +251,7 @@ class ProjectConfig
243
251
 
244
252
  @jira_url = json['jira_url']
245
253
  rescue Errno::ENOENT
246
- puts "== Can't load files from the target directory. Did you forget to download first? =="
254
+ file_system.log "Can't load #{filename}. Have you done a download?", also_write_to_stderr: true
247
255
  raise
248
256
  end
249
257
 
@@ -259,7 +267,7 @@ class ProjectConfig
259
267
  unless all_boards&.size == 1
260
268
  message = "If the board_id isn't set then we look for all board configurations in the target" \
261
269
  ' directory. '
262
- if all_boards.nil? || all_boards.empty?
270
+ if all_boards.empty?
263
271
  message += ' In this case, we couldn\'t find any configuration files in the target directory.'
264
272
  else
265
273
  message += 'If there is only one, we use that. In this case we found configurations for' \
@@ -291,21 +299,21 @@ class ProjectConfig
291
299
  end
292
300
 
293
301
  def issues
294
- raise "issues are being loaded before boards in project #{name.inspect}" if all_boards.nil? && !aggregated_project?
295
-
296
302
  unless @issues
297
- if @aggregate_config
303
+ if aggregated_project?
298
304
  raise 'This is an aggregated project and issues should have been included with the include_issues_from ' \
299
305
  'declaration but none are here. Check your config.'
300
306
  end
307
+ load_data if all_boards.empty?
301
308
 
302
309
  timezone_offset = exporter.timezone_offset
303
310
 
304
- issues_path = "#{@target_path}#{file_prefix}_issues/"
311
+ issues_path = File.join @target_path, "#{file_prefix}_issues"
305
312
  if File.exist?(issues_path) && File.directory?(issues_path)
306
313
  issues = load_issues_from_issues_directory path: issues_path, timezone_offset: timezone_offset
307
314
  else
308
- puts "Can't find issues in #{path}. Has a download been done?"
315
+ file_system.log "Can't find directory #{issues_path}. Has a download been done?", also_write_to_stderr: true
316
+ return []
309
317
  end
310
318
 
311
319
  # Attach related issues
@@ -351,8 +359,8 @@ class ProjectConfig
351
359
  raise "No boards found for project #{name.inspect}" if all_boards.empty?
352
360
 
353
361
  if all_boards.size != 1
354
- puts "Multiple boards are in use for project #{name.inspect}. " \
355
- "Picked #{default_board.name.inspect} to attach issues to."
362
+ file_system.log "Multiple boards are in use for project #{name.inspect}. " \
363
+ "Picked #{default_board.name.inspect} to attach issues to.", also_write_to_stderr: true
356
364
  end
357
365
  default_board
358
366
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SelfOrIssueDispatcher
4
+ # rubocop:disable Style/ArgumentsForwarding
4
5
  def method_missing method_name, *args, &block
5
6
  raise "#{method_name} isn't a method on Issue or #{self.class}" unless ::Issue.method_defined? method_name.to_sym
6
7
 
@@ -8,6 +9,7 @@ module SelfOrIssueDispatcher
8
9
  issue.__send__ method_name, *args, &block
9
10
  end
10
11
  end
12
+ # rubocop:enable Style/ArgumentsForwarding
11
13
 
12
14
  def respond_to_missing?(method_name, include_all = false)
13
15
  ::Issue.method_defined?(method_name.to_sym) || super
@@ -18,7 +18,7 @@ class SprintBurndown < ChartBase
18
18
  attr_accessor :board_id
19
19
 
20
20
  def initialize
21
- super()
21
+ super
22
22
 
23
23
  @summary_stats = {}
24
24
  header_text 'Sprint burndown'
@@ -126,7 +126,7 @@ class SprintBurndown < ChartBase
126
126
  currently_in_sprint = false
127
127
  change_data = []
128
128
 
129
- issue_completed_time = issue.board.cycletime.stopped_time(issue)
129
+ issue_completed_time = issue.board.cycletime.started_stopped_times(issue).last
130
130
  completed_has_been_tracked = false
131
131
 
132
132
  issue.changes.each do |change|
@@ -38,7 +38,7 @@ class Status
38
38
  end
39
39
 
40
40
  def to_s
41
- result = +"Status(name=#{@name.inspect}," \
41
+ result = "Status(name=#{@name.inspect}," \
42
42
  " id=#{@id.inspect}," \
43
43
  " category_name=#{@category_name.inspect}," \
44
44
  " category_id=#{@category_id.inspect}," \
@@ -68,4 +68,8 @@ class StatusCollection
68
68
  def empty? = @list.empty?
69
69
  def clear = @list.clear
70
70
  def delete(object) = @list.delete(object)
71
+
72
+ def inspect
73
+ "StatusCollection(#{@list.collect(&:inspect).join(', ')})"
74
+ end
71
75
  end
@@ -82,7 +82,7 @@ class ThroughputChart < ChartBase
82
82
  def throughput_dataset periods:, completed_issues:
83
83
  periods.collect do |period|
84
84
  closed_issues = completed_issues.filter_map do |issue|
85
- stop_date = issue.board.cycletime.stopped_time(issue)&.to_date
85
+ stop_date = issue.board.cycletime.started_stopped_times(issue).last&.to_date
86
86
  [stop_date, issue] if stop_date && period.include?(stop_date)
87
87
  end
88
88
 
data/lib/jirametrics.rb CHANGED
@@ -3,9 +3,13 @@
3
3
  require 'thor'
4
4
 
5
5
  class JiraMetrics < Thor
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+
6
10
  option :config
7
11
  option :name
8
- desc 'export only', "Export data into either reports or CSV's as per the configuration"
12
+ desc 'export', "Export data into either reports or CSV's as per the configuration"
9
13
  def export
10
14
  load_config options[:config]
11
15
  Exporter.instance.export(name_filter: options[:name] || '*')
@@ -13,7 +17,7 @@ class JiraMetrics < Thor
13
17
 
14
18
  option :config
15
19
  option :name
16
- desc 'download only', 'Download data from Jira'
20
+ desc 'download', 'Download data from Jira'
17
21
  def download
18
22
  load_config options[:config]
19
23
  Exporter.instance.download(name_filter: options[:name] || '*')
@@ -21,7 +25,7 @@ class JiraMetrics < Thor
21
25
 
22
26
  option :config
23
27
  option :name
24
- desc 'download and export', 'Same as running download, followed by export'
28
+ desc 'go', 'Same as running download, followed by export'
25
29
  def go
26
30
  load_config options[:config]
27
31
  Exporter.instance.download(name_filter: options[:name] || '*')
@@ -30,6 +34,13 @@ class JiraMetrics < Thor
30
34
  Exporter.instance.export(name_filter: options[:name] || '*')
31
35
  end
32
36
 
37
+ option :config
38
+ desc 'info', 'Dump information about one issue'
39
+ def info keys
40
+ load_config options[:config]
41
+ Exporter.instance.info(keys, name_filter: options[:name] || '*')
42
+ end
43
+
33
44
  private
34
45
 
35
46
  def load_config config_file
@@ -69,6 +80,7 @@ class JiraMetrics < Thor
69
80
  require 'jirametrics/daily_wip_by_parent_chart'
70
81
  require 'jirametrics/aging_work_in_progress_chart'
71
82
  require 'jirametrics/cycletime_scatterplot'
83
+ require 'jirametrics/flow_efficiency_scatterplot'
72
84
  require 'jirametrics/sprint_issue_change_data'
73
85
  require 'jirametrics/cycletime_histogram'
74
86
  require 'jirametrics/daily_wip_by_blocked_stalled_chart'
@@ -97,6 +109,4 @@ class JiraMetrics < Thor
97
109
  require 'jirametrics/board'
98
110
  load config_file
99
111
  end
100
-
101
- # Dir.foreach('lib/jirametrics') {|file| puts "require 'jirametrics/#{$1}'" if file =~ /^(.+)\.rb$/}
102
112
  end
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.6'
4
+ version: 2.7.1
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-10-02 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: random-word
@@ -95,6 +95,7 @@ files:
95
95
  - lib/jirametrics/file_config.rb
96
96
  - lib/jirametrics/file_system.rb
97
97
  - lib/jirametrics/fix_version.rb
98
+ - lib/jirametrics/flow_efficiency_scatterplot.rb
98
99
  - lib/jirametrics/groupable_issue_chart.rb
99
100
  - lib/jirametrics/grouping_rules.rb
100
101
  - lib/jirametrics/hierarchy_table.rb
@@ -105,9 +106,9 @@ files:
105
106
  - lib/jirametrics/html/cycletime_histogram.erb
106
107
  - lib/jirametrics/html/cycletime_scatterplot.erb
107
108
  - lib/jirametrics/html/daily_wip_chart.erb
108
- - lib/jirametrics/html/data_quality_report.erb
109
109
  - lib/jirametrics/html/estimate_accuracy_chart.erb
110
110
  - lib/jirametrics/html/expedited_chart.erb
111
+ - lib/jirametrics/html/flow_efficiency_scatterplot.erb
111
112
  - lib/jirametrics/html/hierarchy_table.erb
112
113
  - lib/jirametrics/html/index.css
113
114
  - lib/jirametrics/html/index.erb
@@ -130,14 +131,14 @@ files:
130
131
  - lib/jirametrics/tree_organizer.rb
131
132
  - lib/jirametrics/trend_line_calculator.rb
132
133
  - lib/jirametrics/value_equality.rb
133
- homepage: https://github.com/mikebowler/jirametrics
134
+ homepage: https://jirametrics.org
134
135
  licenses:
135
136
  - Apache-2.0
136
137
  metadata:
137
138
  rubygems_mfa_required: 'true'
138
139
  bug_tracker_uri: https://github.com/mikebowler/jirametrics/issues
139
- changelog_uri: https://github.com/mikebowler/jirametrics/wiki/Changes
140
- documentation_uri: https://github.com/mikebowler/jirametrics/wiki
140
+ changelog_uri: https://jirametrics.org/changes
141
+ documentation_uri: https://jirametrics.org
141
142
  post_install_message:
142
143
  rdoc_options: []
143
144
  require_paths:
@@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
154
  - !ruby/object:Gem::Version
154
155
  version: '0'
155
156
  requirements: []
156
- rubygems_version: 3.5.18
157
+ rubygems_version: 3.5.21
157
158
  signing_key:
158
159
  specification_version: 4
159
160
  summary: Extract Jira metrics
@@ -1,126 +0,0 @@
1
- <%
2
- problems = problems_for :discarded_changes
3
- unless problems.empty?
4
- %>
5
- <p>
6
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> have had information discarded. This configuration is set
7
- to "reset the clock" if an item is moved back to the backlog after it's been started. This hides important
8
- information and makes the data less accurate. <b>Moving items back to the backlog is strongly discouraged.</b>
9
- <%= collapsible_issues_panel problems %>
10
- </p>
11
- <%
12
- end
13
- %>
14
-
15
- <%
16
- problems = problems_for :completed_but_not_started
17
- unless problems.empty?
18
- percentage_work_included = ((issues.size - problems.size).to_f / issues.size * 100).to_i
19
- %>
20
- <p>
21
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> were discarded from all charts using cycletime (scatterplot, histogram, etc) as we couldn't determine when they started.
22
- <% if percentage_work_included < 85 %>
23
- Consider whether looking at only <%= percentage_work_included %>% of the total data points is enough to come to any reasonable conclusions. See <a href="https://en.wikipedia.org/wiki/Survivorship_bias">Survivorship Bias</a>.
24
- <% end %>
25
- <%= collapsible_issues_panel problems %>
26
- </p>
27
- <%
28
- end
29
- %>
30
-
31
- <%
32
- problems = problems_for :status_changes_after_done
33
- unless problems.empty?
34
- %>
35
- <p>
36
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> had a status change after being identified as done. We should question whether they were really done at that point or if we stopped the clock too early.
37
- <%= collapsible_issues_panel problems %>
38
- </p>
39
- <%
40
- end
41
- %>
42
-
43
- <%
44
- problems = problems_for :backwards_through_status_categories
45
- unless problems.empty?
46
- %>
47
- <p>
48
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> moved backwards across the board, <b>crossing status categories</b>. This will almost certainly have impacted timings as the end times are often taken at status category boundaries. You should assume that any timing measurements for this item are wrong.
49
- <%= collapsible_issues_panel problems %>
50
- </p>
51
- <%
52
- end
53
- %>
54
-
55
- <%
56
- problems = problems_for :backwords_through_statuses
57
- unless problems.empty?
58
- %>
59
- <p>
60
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> moved backwards across the board. Depending where we have set the start and end points, this may give us incorrect timing data. Note that these items did not cross a status category and may not have affected metrics.
61
- <%= collapsible_issues_panel problems %>
62
- </p>
63
- <%
64
- end
65
- %>
66
-
67
- <%
68
- problems = problems_for :status_not_on_board
69
- unless problems.empty?
70
- %>
71
- <p>
72
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> were not visible on the board for some period of time. This may impact timings as the work was likely to have been forgotten if it wasn't visible.
73
- <%= collapsible_issues_panel problems %>
74
- </p>
75
- <%
76
- end
77
- %>
78
-
79
- <%
80
- problems = problems_for :created_in_wrong_status
81
- unless problems.empty?
82
- %>
83
- <p>
84
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> were created in a status not designated as Backlog. This will impact the measurement of start times and will therefore impact whether it's shown as in progress or not.
85
- <%= collapsible_issues_panel problems %>
86
- </p>
87
- <%
88
- end
89
- %>
90
-
91
- <%
92
- problems = problems_for :stopped_before_started
93
- unless problems.empty?
94
- %>
95
- <p>
96
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> were stopped before they were started and this will play havoc with any cycletime or WIP calculations. The most common case for this is when an item gets closed and then moved back into an in-progress status.
97
- <%= collapsible_issues_panel problems %>
98
- </p>
99
- <%
100
- end
101
- %>
102
-
103
- <%
104
- problems = problems_for :issue_not_started_but_subtasks_have
105
- unless problems.empty?
106
- %>
107
- <p>
108
- <span class="quality_note_bullet">⮕</span> <%= label_issues problems.size %> still showing 'not started' while sub-tasks underneath them have started. This is almost always a mistake; if we're working on subtasks, the top level
109
- item should also have started.
110
- <%= collapsible_issues_panel problems %>
111
- </p>
112
- <%
113
- end
114
- %>
115
-
116
- <%
117
- problems = problems_for :issue_on_multiple_boards
118
- unless problems.empty?
119
- %>
120
- <p>
121
- <span class="quality_note_bullet">⮕</span> For <%= label_issues problems.size %>, we have an issue that shows up on more than one board. This could result in more data points showing up on a chart then there really should be.
122
- <%= collapsible_issues_panel problems, :hide_board_column %>
123
- </p>
124
- <%
125
- end
126
- %>