jirametrics 2.6 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/jirametrics/aggregate_config.rb +6 -1
- data/lib/jirametrics/aging_work_bar_chart.rb +6 -6
- data/lib/jirametrics/aging_work_in_progress_chart.rb +1 -1
- data/lib/jirametrics/aging_work_table.rb +4 -5
- data/lib/jirametrics/blocked_stalled_change.rb +1 -1
- data/lib/jirametrics/board.rb +14 -12
- data/lib/jirametrics/chart_base.rb +16 -10
- data/lib/jirametrics/cycletime_config.rb +26 -7
- data/lib/jirametrics/cycletime_histogram.rb +1 -1
- data/lib/jirametrics/cycletime_scatterplot.rb +3 -6
- data/lib/jirametrics/daily_wip_by_age_chart.rb +2 -4
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +2 -2
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +0 -4
- data/lib/jirametrics/daily_wip_chart.rb +7 -9
- data/lib/jirametrics/data_quality_report.rb +166 -7
- data/lib/jirametrics/dependency_chart.rb +3 -4
- data/lib/jirametrics/discard_changes_before.rb +1 -1
- data/lib/jirametrics/downloader.rb +14 -13
- data/lib/jirametrics/estimate_accuracy_chart.rb +1 -2
- data/lib/jirametrics/examples/aggregated_project.rb +1 -3
- data/lib/jirametrics/examples/standard_project.rb +10 -9
- data/lib/jirametrics/expedited_chart.rb +1 -2
- data/lib/jirametrics/exporter.rb +25 -0
- data/lib/jirametrics/file_system.rb +1 -1
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
- data/lib/jirametrics/html/index.css +10 -3
- data/lib/jirametrics/html_report_config.rb +2 -1
- data/lib/jirametrics/issue.rb +63 -11
- data/lib/jirametrics/jira_gateway.rb +1 -1
- data/lib/jirametrics/project_config.rb +27 -19
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/sprint_burndown.rb +2 -2
- data/lib/jirametrics/status.rb +1 -1
- data/lib/jirametrics/status_collection.rb +4 -0
- data/lib/jirametrics/throughput_chart.rb +1 -1
- data/lib/jirametrics.rb +15 -5
- metadata +8 -7
- 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
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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 = "#{
|
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
|
-
|
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.
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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.
|
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|
|
data/lib/jirametrics/status.rb
CHANGED
@@ -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.
|
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
|
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
|
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 '
|
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:
|
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-
|
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://
|
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://
|
140
|
-
documentation_uri: https://
|
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.
|
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
|
-
%>
|