jirametrics 1.2.1 → 1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/jirametrics/board.rb +4 -0
- data/lib/jirametrics/downloader.rb +1 -1
- data/lib/jirametrics/examples/aggregated_project.rb +4 -0
- data/lib/jirametrics/examples/standard_project.rb +26 -17
- data/lib/jirametrics/issue.rb +3 -11
- data/lib/jirametrics/project_config.rb +27 -16
- data/lib/jirametrics/status.rb +22 -4
- data/lib/jirametrics/status_collection.rb +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62643ff6e83e13e0b414c3969d9ff8dbb6e8eb3fa814413cb15914a5796efaff
|
4
|
+
data.tar.gz: 2b4a18c24fb7ed094f501b1474456de62e69197c63fca79351b9562a2f3d5c5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42a7b6181becd0aeff379e375834ad8c1d4df1bda1dfb3141ded7992d3bb8cc2dbcce607a700ab93dc36044eec85d6c9562f84a9ace6c0dcd0f6cbceded1771e
|
7
|
+
data.tar.gz: b0085ac3089e674c8f3e36d13de345fefd39c0fece121542faf9b29338da3cdee092b84f7bbd55427a162ddc56dd9a8c65d419549471cd6ca5d8f359dab313af
|
data/lib/jirametrics/board.rb
CHANGED
@@ -170,7 +170,7 @@ class Downloader
|
|
170
170
|
@issue_keys_pending_download << parent_key if parent_key
|
171
171
|
|
172
172
|
# Sub-tasks
|
173
|
-
issue.raw['fields']['subtasks']
|
173
|
+
issue.raw['fields']['subtasks']&.each do |raw_subtask|
|
174
174
|
@issue_keys_pending_download << raw_subtask['key']
|
175
175
|
end
|
176
176
|
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# This file is really intended to give you ideas about how you might configure your own reports, not
|
4
|
+
# as a complete setup that will work in every case.
|
5
|
+
#
|
6
|
+
# See https://github.com/mikebowler/jirametrics/wiki/Examples-folder for moreclass Exporter
|
3
7
|
class Exporter
|
4
8
|
def aggregated_project name:, project_names:
|
5
9
|
project name: name do
|
@@ -1,9 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# This file is really intended to give you ideas about how you might configure your own reports, not
|
4
|
+
# as a complete setup that will work in every case.
|
5
|
+
#
|
6
|
+
# See https://github.com/mikebowler/jirametrics/wiki/Examples-folder for more
|
3
7
|
class Exporter
|
4
|
-
def standard_project name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {},
|
8
|
+
def standard_project name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {},
|
9
|
+
default_board: nil, anonymize: false
|
10
|
+
|
5
11
|
project name: name do
|
6
12
|
puts name
|
13
|
+
self.anonymize if anonymize
|
7
14
|
|
8
15
|
settings['blocked_link_text'] = ['is blocked by']
|
9
16
|
file_prefix file_prefix
|
@@ -36,7 +43,7 @@ class Exporter
|
|
36
43
|
html_report do
|
37
44
|
board_id default_board if default_board
|
38
45
|
|
39
|
-
html "<H1>#{
|
46
|
+
html "<H1>#{name}</H1>", type: :header
|
40
47
|
boards.each_key do |id|
|
41
48
|
board = find_board id
|
42
49
|
html "<div><a href='#{board.url}'>#{id} #{board.name}</a></div>",
|
@@ -48,16 +55,7 @@ class Exporter
|
|
48
55
|
cycletime_scatterplot do
|
49
56
|
show_trend_lines
|
50
57
|
end
|
51
|
-
cycletime_scatterplot do # Epics
|
52
|
-
header_text 'Parents only'
|
53
|
-
filter_issues { |i| i.parent }
|
54
|
-
end
|
55
58
|
cycletime_histogram
|
56
|
-
cycletime_histogram do
|
57
|
-
grouping_rules do |issue, rules|
|
58
|
-
rules.label = issue.board.cycletime.stopped_time(issue).to_date.strftime('%b %Y')
|
59
|
-
end
|
60
|
-
end
|
61
59
|
|
62
60
|
throughput_chart do
|
63
61
|
description_text '<h2>Number of items completed, grouped by issue type</h2>'
|
@@ -79,14 +77,24 @@ class Exporter
|
|
79
77
|
aging_work_table
|
80
78
|
daily_wip_by_age_chart
|
81
79
|
daily_wip_by_blocked_stalled_chart
|
80
|
+
daily_wip_chart do
|
81
|
+
header_text 'Daily WIP by Parent'
|
82
|
+
description_text <<-TEXT
|
83
|
+
How much work is in progress, grouped by the parent of the issue. This will give us an
|
84
|
+
indication of how focused we are on higher level objectives. If there are many parent
|
85
|
+
tickets in progress at the same time, either this team has their focus scattered or we
|
86
|
+
aren't doing a good job of
|
87
|
+
<a href="https://improvingflow.com/2024/02/21/slicing-epics.html">splitting those parent
|
88
|
+
tickets</a>. Neither of those is desirable.
|
89
|
+
TEXT
|
90
|
+
grouping_rules do |issue, rules|
|
91
|
+
rules.label = issue.parent&.key || 'No parent'
|
92
|
+
rules.color = 'white' if rules.label == 'No parent'
|
93
|
+
end
|
94
|
+
end
|
82
95
|
expedited_chart
|
83
96
|
sprint_burndown
|
84
97
|
story_point_accuracy_chart
|
85
|
-
# story_point_accuracy_chart do
|
86
|
-
# header_text nil
|
87
|
-
# description_text nil
|
88
|
-
# y_axis(sort_order: %w[Story Task Defect], label: 'TShirt Sizes') { |issue, _started_time| issue.type }
|
89
|
-
# end
|
90
98
|
|
91
99
|
dependency_chart do
|
92
100
|
link_rules do |link, rules|
|
@@ -98,8 +106,9 @@ class Exporter
|
|
98
106
|
rules.merge_bidirectional keep: 'outward'
|
99
107
|
when 'Sync'
|
100
108
|
rules.use_bidirectional_arrows
|
101
|
-
# rules.line_color = 'red'
|
102
109
|
else
|
110
|
+
# This is a link type that we don't recognized. Dump it to standard out to draw attention
|
111
|
+
# to it.
|
103
112
|
puts "name=#{link.name}, label=#{link.label}"
|
104
113
|
end
|
105
114
|
end
|
data/lib/jirametrics/issue.rb
CHANGED
@@ -51,15 +51,7 @@ class Issue
|
|
51
51
|
def summary = @raw['fields']['summary']
|
52
52
|
|
53
53
|
def status
|
54
|
-
|
55
|
-
raw_category = raw_status['statusCategory']
|
56
|
-
|
57
|
-
Status.new(
|
58
|
-
name: raw_status['name'],
|
59
|
-
id: raw_status['id'].to_i,
|
60
|
-
category_name: raw_category['name'],
|
61
|
-
category_id: raw_category['id'].to_i
|
62
|
-
)
|
54
|
+
Status.new raw: @raw['fields']['status']
|
63
55
|
end
|
64
56
|
|
65
57
|
def status_id
|
@@ -495,7 +487,7 @@ class Issue
|
|
495
487
|
end
|
496
488
|
|
497
489
|
def load_history_into_changes
|
498
|
-
@raw['changelog']['histories']
|
490
|
+
@raw['changelog']['histories']&.each do |history|
|
499
491
|
created = parse_time(history['created'])
|
500
492
|
|
501
493
|
# It should be impossible to not have an author but we've seen it in production
|
@@ -507,7 +499,7 @@ class Issue
|
|
507
499
|
end
|
508
500
|
|
509
501
|
def load_comments_into_changes
|
510
|
-
@raw['fields']['comment']['comments']
|
502
|
+
@raw['fields']['comment']['comments']&.each do |comment|
|
511
503
|
raw = {
|
512
504
|
'field' => 'comment',
|
513
505
|
'to' => comment['id'],
|
@@ -7,8 +7,9 @@ class ProjectConfig
|
|
7
7
|
include DiscardChangesBefore
|
8
8
|
|
9
9
|
attr_reader :target_path, :jira_config, :all_boards, :possible_statuses,
|
10
|
-
:download_config, :file_configs, :exporter, :data_version, :name, :board_configs,
|
11
|
-
|
10
|
+
:download_config, :file_configs, :exporter, :data_version, :name, :board_configs,
|
11
|
+
:settings
|
12
|
+
attr_accessor :time_range, :jira_url, :project_id
|
12
13
|
|
13
14
|
def initialize exporter:, jira_config:, block:, target_path: '.', name: ''
|
14
15
|
@exporter = exporter
|
@@ -39,8 +40,9 @@ class ProjectConfig
|
|
39
40
|
|
40
41
|
def run
|
41
42
|
unless aggregated_project?
|
42
|
-
load_status_category_mappings
|
43
43
|
load_all_boards
|
44
|
+
@project_id = @all_boards.first.last&.project_id
|
45
|
+
load_status_category_mappings
|
44
46
|
load_project_metadata
|
45
47
|
load_sprints
|
46
48
|
end
|
@@ -99,7 +101,7 @@ class ProjectConfig
|
|
99
101
|
return
|
100
102
|
end
|
101
103
|
|
102
|
-
add_possible_status Status.new(name: status,
|
104
|
+
add_possible_status Status.new(name: status, category_name: category)
|
103
105
|
end
|
104
106
|
|
105
107
|
def load_all_boards
|
@@ -170,14 +172,7 @@ class ProjectConfig
|
|
170
172
|
end
|
171
173
|
|
172
174
|
status_json_snippets.each do |snippet|
|
173
|
-
|
174
|
-
status_name = snippet['name']
|
175
|
-
add_possible_status Status.new(
|
176
|
-
name: status_name,
|
177
|
-
id: snippet['id'].to_i,
|
178
|
-
category_name: category_config['name'],
|
179
|
-
category_id: category_config['id'].to_i
|
180
|
-
)
|
175
|
+
add_possible_status Status.new(raw: snippet)
|
181
176
|
end
|
182
177
|
end
|
183
178
|
|
@@ -198,16 +193,32 @@ class ProjectConfig
|
|
198
193
|
end
|
199
194
|
|
200
195
|
def add_possible_status status
|
196
|
+
# If it's project scoped and it's not this project, just ignore it.
|
197
|
+
return if status.project_id && (@project_id.nil? || status.project_id != @project_id)
|
198
|
+
|
201
199
|
existing_status = find_status(name: status.name)
|
202
200
|
|
203
|
-
|
204
|
-
|
205
|
-
raise "Redefining status category #{status} with #{existing_status}. Was one set in the config?"
|
206
|
-
end
|
201
|
+
# If it isn't there, add it and go.
|
202
|
+
return @possible_statuses << status unless existing_status
|
207
203
|
|
204
|
+
# If the existing one has a project id then it's already the most precise. Ignore the new one.
|
205
|
+
# No need to check categories as status_category_mapping can't add a project_id so by definition
|
206
|
+
# this data came from Jira.
|
207
|
+
return if existing_status && existing_status.project_id
|
208
|
+
|
209
|
+
# If the new one has a project_id then it's more precise so replace the old one with this,
|
210
|
+
# regardless of whether the categories match.
|
211
|
+
if status.project_id
|
212
|
+
@possible_statuses.delete(existing_status)
|
213
|
+
@possible_statuses << status
|
208
214
|
return
|
209
215
|
end
|
210
216
|
|
217
|
+
# This new status may have come from status_category_mapping so verify that categories match.
|
218
|
+
if existing_status.category_name != status.category_name
|
219
|
+
raise "Redefining status category #{status} with #{existing_status}. Was one set in the config?"
|
220
|
+
end
|
221
|
+
|
211
222
|
@possible_statuses << status
|
212
223
|
end
|
213
224
|
|
data/lib/jirametrics/status.rb
CHANGED
@@ -1,19 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Status
|
4
|
-
attr_reader :id, :type, :category_name, :category_id
|
4
|
+
attr_reader :id, :type, :category_name, :category_id, :project_id
|
5
5
|
attr_accessor :name
|
6
6
|
|
7
|
-
def initialize name
|
7
|
+
def initialize name: nil, id: nil, category_name: nil, category_id: nil, project_id: nil, raw: nil
|
8
8
|
@name = name
|
9
9
|
@id = id
|
10
10
|
@category_name = category_name
|
11
11
|
@category_id = category_id
|
12
|
+
@project_id = project_id
|
13
|
+
|
14
|
+
if raw
|
15
|
+
@raw = raw
|
16
|
+
@name = raw['name']
|
17
|
+
@id = raw['id'].to_i
|
18
|
+
|
19
|
+
category_config = raw['statusCategory']
|
20
|
+
@category_name = category_config['name']
|
21
|
+
@category_id = category_config['id'].to_i
|
22
|
+
|
23
|
+
# If this is a NextGen project then this status may be project specific. When this field is
|
24
|
+
# nil then the status is global.
|
25
|
+
@project_id = raw['scope']&.[]('project')&.[]('id')
|
26
|
+
end
|
27
|
+
|
12
28
|
end
|
13
29
|
|
14
30
|
def to_s
|
15
31
|
"Status(name=#{@name.inspect}, id=#{@id.inspect}," \
|
16
|
-
" category_name=#{@category_name.inspect}, category_id=#{@category_id.inspect})"
|
32
|
+
" category_name=#{@category_name.inspect}, category_id=#{@category_id.inspect}, project_id=#{@project_id})"
|
17
33
|
end
|
18
34
|
|
19
35
|
def eql?(other)
|
@@ -21,6 +37,8 @@ class Status
|
|
21
37
|
end
|
22
38
|
|
23
39
|
def state
|
24
|
-
instance_variables
|
40
|
+
instance_variables
|
41
|
+
.reject {|variable| variable == :@raw}
|
42
|
+
.map { |variable| instance_variable_get variable }
|
25
43
|
end
|
26
44
|
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: 1.
|
4
|
+
version: '1.4'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bowler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: random-word
|
@@ -163,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
163
|
- !ruby/object:Gem::Version
|
164
164
|
version: '0'
|
165
165
|
requirements: []
|
166
|
-
rubygems_version: 3.
|
166
|
+
rubygems_version: 3.5.7
|
167
167
|
signing_key:
|
168
168
|
specification_version: 4
|
169
169
|
summary: Extract Jira metrics
|