jirametrics 1.2.1 → 1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e97ffb4b2217f2b52ad32442b150243308aed066d4d4cedea4e9ab00a54e2f60
4
- data.tar.gz: 4d722736c3494db4977ce2504afe69ba5dad00982b25069a5dea70ff2b7141bc
3
+ metadata.gz: 02be53200d02a09c1dd8829dba1a06147959d6fc7c14960e810497ab72c8c731
4
+ data.tar.gz: b3f79887cc78af19ec5199140bb655cb161a21fc548d52b98fee04c287fdc100
5
5
  SHA512:
6
- metadata.gz: bec679ade15523af3929183d52603d200a4dc42ca1cb34debbde314970d2883f93b40f1f05789497d16ba467b812fc9f9df90cfe34cd1dff81ad7f04b7cf9b35
7
- data.tar.gz: 578a6ef7ba619bfd63e3ff5c2653fddca52500ad3f03117b043c80eed039c0b437eae7536f6970f9b8bfa1b03a2fd78b706abe015167350a506d0844313200af
6
+ metadata.gz: d05d5ea737475206c127b3e1bde806a5fb98493f4cd5d75dbc7d0ce1d89093fb5778ae276f1189a37096a6f800550f7f1bb86a2bbf1e9433f80caa90cc905ba8
7
+ data.tar.gz: 82dd49447e33017bd5d0dfb527011d27ae018d32c60e310bb8cc30ca1bcc8265888264df7aa2387109d8fe822a75381e6687bd750923de0d742b28dfb153bdf6
@@ -83,6 +83,10 @@ class Board
83
83
  @raw['id'].to_i
84
84
  end
85
85
 
86
+ def project_id
87
+ @raw['location']['id']
88
+ end
89
+
86
90
  def name
87
91
  @raw['name']
88
92
  end
@@ -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'].each do |raw_subtask|
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: {}, default_board: nil
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>#{file_prefix}</H1>", type: :header
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
@@ -495,7 +495,7 @@ class Issue
495
495
  end
496
496
 
497
497
  def load_history_into_changes
498
- @raw['changelog']['histories'].each do |history|
498
+ @raw['changelog']['histories']&.each do |history|
499
499
  created = parse_time(history['created'])
500
500
 
501
501
  # It should be impossible to not have an author but we've seen it in production
@@ -507,7 +507,7 @@ class Issue
507
507
  end
508
508
 
509
509
  def load_comments_into_changes
510
- @raw['fields']['comment']['comments'].each do |comment|
510
+ @raw['fields']['comment']['comments']&.each do |comment|
511
511
  raw = {
512
512
  'field' => 'comment',
513
513
  '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, :settings
11
- attr_accessor :time_range, :jira_url
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
@@ -176,7 +178,8 @@ class ProjectConfig
176
178
  name: status_name,
177
179
  id: snippet['id'].to_i,
178
180
  category_name: category_config['name'],
179
- category_id: category_config['id'].to_i
181
+ category_id: category_config['id'].to_i,
182
+ project_id: snippet['scope']&.[]('project')&.[]('id') # May have a value if this is a NextGen project
180
183
  )
181
184
  end
182
185
  end
@@ -198,16 +201,32 @@ class ProjectConfig
198
201
  end
199
202
 
200
203
  def add_possible_status status
204
+ # If it's project scoped and it's not this project, just ignore it.
205
+ return if status.project_id && status.project_id != @project_id
206
+
201
207
  existing_status = find_status(name: status.name)
202
208
 
203
- if existing_status
204
- if existing_status.category_name != status.category_name
205
- raise "Redefining status category #{status} with #{existing_status}. Was one set in the config?"
206
- end
209
+ # If it isn't there, add it and go.
210
+ return @possible_statuses << status unless existing_status
207
211
 
212
+ # If the existing one has a project id then it's already the most precise. Ignore the new one.
213
+ # No need to check categories as status_category_mapping can't add a project_id so by definition
214
+ # this data came from Jira.
215
+ return if existing_status && existing_status.project_id
216
+
217
+ # If the new one has a project_id then it's more precise so replace the old one with this,
218
+ # regardless of whether the categories match.
219
+ if status.project_id
220
+ @possible_statuses.delete(existing_status)
221
+ @possible_statuses << status
208
222
  return
209
223
  end
210
224
 
225
+ # This new status may have come from status_category_mapping so verify that categories match.
226
+ if existing_status.category_name != status.category_name
227
+ raise "Redefining status category #{status} with #{existing_status}. Was one set in the config?"
228
+ end
229
+
211
230
  @possible_statuses << status
212
231
  end
213
232
 
@@ -1,19 +1,20 @@
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:, id:, category_name:, category_id:
7
+ def initialize name:, id:, category_name:, category_id:, project_id: 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
12
13
  end
13
14
 
14
15
  def to_s
15
16
  "Status(name=#{@name.inspect}, id=#{@id.inspect}," \
16
- " category_name=#{@category_name.inspect}, category_id=#{@category_id.inspect})"
17
+ " category_name=#{@category_name.inspect}, category_id=#{@category_id.inspect}, project_id=#{@project_id})"
17
18
  end
18
19
 
19
20
  def eql?(other)
@@ -64,4 +64,5 @@ class StatusCollection
64
64
  def <<(arg) = @list << arg
65
65
  def empty? = @list.empty?
66
66
  def clear = @list.clear
67
+ def delete(object) = @list.delete(object)
67
68
  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.2.1
4
+ version: '1.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-05 00:00:00.000000000 Z
11
+ date: 2024-04-05 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.2.15
166
+ rubygems_version: 3.5.7
167
167
  signing_key:
168
168
  specification_version: 4
169
169
  summary: Extract Jira metrics