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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e97ffb4b2217f2b52ad32442b150243308aed066d4d4cedea4e9ab00a54e2f60
4
- data.tar.gz: 4d722736c3494db4977ce2504afe69ba5dad00982b25069a5dea70ff2b7141bc
3
+ metadata.gz: 62643ff6e83e13e0b414c3969d9ff8dbb6e8eb3fa814413cb15914a5796efaff
4
+ data.tar.gz: 2b4a18c24fb7ed094f501b1474456de62e69197c63fca79351b9562a2f3d5c5d
5
5
  SHA512:
6
- metadata.gz: bec679ade15523af3929183d52603d200a4dc42ca1cb34debbde314970d2883f93b40f1f05789497d16ba467b812fc9f9df90cfe34cd1dff81ad7f04b7cf9b35
7
- data.tar.gz: 578a6ef7ba619bfd63e3ff5c2653fddca52500ad3f03117b043c80eed039c0b437eae7536f6970f9b8bfa1b03a2fd78b706abe015167350a506d0844313200af
6
+ metadata.gz: 42a7b6181becd0aeff379e375834ad8c1d4df1bda1dfb3141ded7992d3bb8cc2dbcce607a700ab93dc36044eec85d6c9562f84a9ace6c0dcd0f6cbceded1771e
7
+ data.tar.gz: b0085ac3089e674c8f3e36d13de345fefd39c0fece121542faf9b29338da3cdee092b84f7bbd55427a162ddc56dd9a8c65d419549471cd6ca5d8f359dab313af
@@ -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
@@ -51,15 +51,7 @@ class Issue
51
51
  def summary = @raw['fields']['summary']
52
52
 
53
53
  def status
54
- raw_status = @raw['fields']['status']
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'].each do |history|
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'].each do |comment|
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, :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
@@ -99,7 +101,7 @@ class ProjectConfig
99
101
  return
100
102
  end
101
103
 
102
- add_possible_status Status.new(name: status, id: nil, category_name: category, category_id: nil)
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
- category_config = snippet['statusCategory']
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
- 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
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
 
@@ -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:, id:, category_name:, category_id:
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.map { |variable| instance_variable_get variable }
40
+ instance_variables
41
+ .reject {|variable| variable == :@raw}
42
+ .map { |variable| instance_variable_get variable }
25
43
  end
26
44
  end
@@ -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.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: 2023-10-05 00:00:00.000000000 Z
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.2.15
166
+ rubygems_version: 3.5.7
167
167
  signing_key:
168
168
  specification_version: 4
169
169
  summary: Extract Jira metrics