jirametrics 2.4 → 2.5pre1

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: 142ac2bba90f603ef9b6d2243b688e91259c5b0437cd7e4458f1a9fd9a4bcc69
4
- data.tar.gz: b9111ee6827f383b5db257da17ec284a333d98183f4a47f1b8c86b388e7b37f3
3
+ metadata.gz: 36ab7f032ba0b995401c3fc0ca9a11c353b3390284a668af73770ebd82b4eb77
4
+ data.tar.gz: cf843b3245b89cd7af6c00a0d82449bb5e10d957e3af997b7288d0b9360b8867
5
5
  SHA512:
6
- metadata.gz: ead0cc6937f2b0ce3e060dee4771bb8e585bb26962113c7589fb13222966e13ae76de18e68e148e17ebccfe8c9e2c9f51f9b5b1adb3ef2b4f94fac0a0e848613
7
- data.tar.gz: 55add3d6887f9e16800f5ce665c3ada6bb9b3c2a980241ab2adbf0afb517ff5d2da3b4875f1c4caf161e5e493ece57e40687d672be012c06945588da8fc2f22e
6
+ metadata.gz: 305166ab16e9abde2dbde0a01b5c60b46ff8f7654d4d4cb4b222777bb6222193d165cfcb9bc863e129eb599b6f63ee6e3ef6c19800cb22ffa39d9ca1933b5978
7
+ data.tar.gz: f72db2f5f5f408d51457e09d5fc43188487c8ba8dbefa6ace9dc1e90ec428a0f65e1c7828808f41d1d697365bcacdbd0faec51452a01344d7ec0a32fd6cb5a02
@@ -2,14 +2,13 @@
2
2
 
3
3
  class Board
4
4
  attr_reader :visible_columns, :raw, :possible_statuses, :sprints, :backlog_statuses
5
- attr_accessor :cycletime, :project_config, :expedited_priority_names
5
+ attr_accessor :cycletime, :project_config
6
6
 
7
7
  def initialize raw:, possible_statuses: StatusCollection.new
8
8
  @raw = raw
9
9
  @board_type = raw['type']
10
10
  @possible_statuses = possible_statuses
11
11
  @sprints = []
12
- @expedited_priority_names = []
13
12
 
14
13
  columns = raw['columnConfig']['columns']
15
14
 
@@ -11,7 +11,6 @@ class BoardConfig
11
11
 
12
12
  def run
13
13
  @board = @project_config.all_boards[id]
14
- @board.expedited_priority_names = []
15
14
 
16
15
  instance_eval(&@block)
17
16
  end
@@ -26,6 +25,7 @@ class BoardConfig
26
25
  end
27
26
 
28
27
  def expedited_priority_names *priority_names
29
- @board.expedited_priority_names = priority_names
28
+ deprecated date: '2024-09-15', message: 'Expedited priority names are now specified in settings'
29
+ @project_config.settings['expedited_priority_names'] = priority_names
30
30
  end
31
31
  end
@@ -42,17 +42,13 @@ class DependencyChart < ChartBase
42
42
  HTML
43
43
 
44
44
  @rules_block = rules_block
45
- @link_rules_block = ->(link_name, link_rules) {}
46
45
 
47
- issue_rules do |issue, rules|
48
- key = issue.key
49
- key = "<S>#{key} </S> " if issue.status.category_name == 'Done'
50
- rules.label = "<#{key} [#{issue.type}]<BR/>#{word_wrap issue.summary}>"
51
- end
46
+ issue_rules(&default_issue_rules)
47
+ link_rules(&default_link_rules)
52
48
  end
53
49
 
54
50
  def run
55
- instance_eval(&@rules_block)
51
+ instance_eval(&@rules_block) if @rules_block
56
52
 
57
53
  dot_graph = build_dot_graph
58
54
  return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if dot_graph.nil?
@@ -219,4 +215,36 @@ class DependencyChart < ChartBase
219
215
  end
220
216
  end.join(separator)
221
217
  end
218
+
219
+ def default_issue_rules
220
+ chart = self
221
+ lambda do |issue, rules|
222
+ is_done = issue.done?
223
+
224
+ key = issue.key
225
+ key = "<S>#{key} </S> " if is_done
226
+ line2 = +'<BR/>'
227
+ if issue.artificial?
228
+ line2 << '(unknown state)' # Shouldn't happen if we've done a full download but is still possible.
229
+ elsif is_done
230
+ line2 << 'Done'
231
+ else
232
+ started_at = issue.board.cycletime.started_time(issue)
233
+ if started_at.nil?
234
+ line2 << 'Not started'
235
+ else
236
+ line2 << "Age: #{issue.board.cycletime.age(issue, today: chart.date_range.end)} days"
237
+ end
238
+ end
239
+ rules.label = "<#{key} [#{issue.type}]#{line2}<BR/>#{word_wrap issue.summary}>"
240
+ end
241
+ end
242
+
243
+ def default_link_rules
244
+ lambda do |link, rules|
245
+ rules.ignore if link.origin.done? && link.other_issue.done?
246
+ rules.ignore if link.name == 'Cloners'
247
+ rules.merge_bidirectional keep: 'outward'
248
+ end
249
+ end
222
250
  end
@@ -19,4 +19,16 @@ class DownloadConfig
19
19
  @rolling_date_count = count unless count.nil?
20
20
  @rolling_date_count
21
21
  end
22
+
23
+ def no_earlier_than date = nil
24
+ @no_earlier_than = Date.parse(date) unless date.nil?
25
+ @no_earlier_than
26
+ end
27
+
28
+ def start_date today:
29
+ date = today.to_date - @rolling_date_count if @rolling_date_count
30
+ date = [date, @no_earlier_than].max if date && @no_earlier_than
31
+ date = @no_earlier_than if date.nil? && @no_earlier_than
32
+ date
33
+ end
22
34
  end
@@ -199,7 +199,11 @@ class Downloader
199
199
  )
200
200
  is_last = json['isLast']
201
201
  max_results = json['maxResults']
202
- start_at += json['values'].size
202
+ if json['values']
203
+ start_at += json['values'].size
204
+ else
205
+ log " No sprints found for board #{board_id}"
206
+ end
203
207
  end
204
208
  end
205
209
 
@@ -271,8 +275,10 @@ class Downloader
271
275
  segments = []
272
276
  segments << "filter=#{filter_id}"
273
277
 
274
- unless @download_config.rolling_date_count.nil?
275
- @download_date_range = (today.to_date - @download_config.rolling_date_count)..today.to_date
278
+ start_date = @download_config.start_date today: today
279
+
280
+ if start_date
281
+ @download_date_range = start_date..today.to_date
276
282
 
277
283
  # For an incremental download, we want to query from the end of the previous one, not from the
278
284
  # beginning of the full range.
@@ -64,25 +64,14 @@ class Exporter
64
64
 
65
65
  # By default, the issue doesn't show what board it's on and this is important for an
66
66
  # aggregated view
67
+ chart = self
67
68
  issue_rules do |issue, rules|
68
- key = issue.key
69
- key = "<S>#{key} </S> " if issue.status.category_name == 'Done'
70
- rules.label = "<#{key} [#{issue.type}]<BR/>#{issue.board.name}<BR/>#{word_wrap issue.summary}>"
69
+ chart.default_issue_rules.call(issue, rules)
70
+ rules.label = rules.label.split('<BR/>').insert(1, "Board: #{issue.board.name}").join('<BR/>')
71
71
  end
72
72
 
73
73
  link_rules do |link, rules|
74
- # By default, the dependency chart shows everything. Clean it up a bit.
75
- case link.name
76
- when 'Cloners'
77
- # We don't want to see any clone links at all.
78
- rules.ignore
79
- when 'Blocks'
80
- # For blocks, by default Jira will have links going both
81
- # ways and we want them only going one way. Also make the
82
- # link red.
83
- rules.merge_bidirectional keep: 'outward'
84
- rules.line_color = 'red'
85
- end
74
+ chart.default_link_rules.call(link, rules)
86
75
 
87
76
  # Because this is the aggregated view, let's hide any link that doesn't cross boards.
88
77
  rules.ignore if link.origin.board == link.other_issue.board
@@ -6,13 +6,13 @@
6
6
  # See https://github.com/mikebowler/jirametrics/wiki/Examples-folder for more
7
7
  class Exporter
8
8
  def standard_project name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {},
9
- default_board: nil, anonymize: false, settings: {}, status_category_mappings: {}
9
+ default_board: nil, anonymize: false, settings: {}, status_category_mappings: {},
10
+ rolling_date_count: 90, no_earlier_than: nil
10
11
 
11
12
  project name: name do
12
13
  puts name
13
14
  self.anonymize if anonymize
14
15
 
15
- settings['blocked_link_text'] = ['is blocked by']
16
16
  self.settings.merge! settings
17
17
 
18
18
  status_category_mappings.each do |status, category|
@@ -21,7 +21,8 @@ class Exporter
21
21
 
22
22
  file_prefix file_prefix
23
23
  download do
24
- rolling_date_count 90
24
+ self.rolling_date_count(rolling_date_count) if rolling_date_count
25
+ self.no_earlier_than(no_earlier_than) if no_earlier_than
25
26
  end
26
27
 
27
28
  boards.each_key do |board_id|
@@ -34,7 +35,6 @@ class Exporter
34
35
  end
35
36
  board id: board_id do
36
37
  cycletime(&block)
37
- expedited_priority_names 'Critical', 'Highest', 'Immediate Gating'
38
38
  end
39
39
  end
40
40
 
@@ -87,24 +87,7 @@ class Exporter
87
87
  expedited_chart
88
88
  sprint_burndown
89
89
  estimate_accuracy_chart
90
-
91
- dependency_chart do
92
- link_rules do |link, rules|
93
- case link.name
94
- when 'Cloners'
95
- rules.ignore
96
- when 'Dependency', 'Blocks', 'Parent/Child', 'Cause', 'Satisfy Requirement', 'Relates'
97
- rules.merge_bidirectional keep: 'outward'
98
- rules.merge_bidirectional keep: 'outward'
99
- when 'Sync'
100
- rules.use_bidirectional_arrows
101
- else
102
- # This is a link type that we don't recognize. Dump it to standard out to draw attention
103
- # to it.
104
- puts "name=#{link.name}, label=#{link.label}"
105
- end
106
- end
107
- end
90
+ dependency_chart
108
91
  end
109
92
  end
110
93
  end
@@ -57,7 +57,7 @@ class ExpeditedChart < ChartBase
57
57
  def prepare_expedite_data issue
58
58
  expedite_start = nil
59
59
  result = []
60
- expedited_priority_names = issue.board.expedited_priority_names
60
+ expedited_priority_names = issue.board.project_config.settings['expedited_priority_names']
61
61
 
62
62
  issue.changes.each do |change|
63
63
  next unless change.priority?
@@ -7,7 +7,9 @@ class Object
7
7
  text = +''
8
8
  text << "Deprecated(#{date}): "
9
9
  text << message
10
- text << "\n-> Called from #{caller(1..1).first}"
10
+ caller(1..2).each do |line|
11
+ text << "\n-> Called from #{line}"
12
+ end
11
13
  warn text
12
14
  end
13
15
  end
@@ -371,7 +371,9 @@ class Issue
371
371
  end
372
372
 
373
373
  def expedited?
374
- names = @board&.expedited_priority_names
374
+ return false unless @board&.project_config
375
+
376
+ names = @board.project_config.settings['expedited_priority_names']
375
377
  return false unless names
376
378
 
377
379
  current_priority = raw['fields']['priority']&.[]('name')
@@ -380,7 +382,9 @@ class Issue
380
382
 
381
383
  def expedited_on_date? date
382
384
  expedited_start = nil
383
- expedited_names = @board&.expedited_priority_names
385
+ return false unless @board&.project_config
386
+
387
+ expedited_names = @board.project_config.settings['expedited_priority_names']
384
388
 
385
389
  changes.each do |change|
386
390
  next unless change.priority?
@@ -433,6 +437,10 @@ class Issue
433
437
  @fix_versions
434
438
  end
435
439
 
440
+ def looks_like_issue_key? key
441
+ !!(key.is_a?(String) && key =~ /^[^-]+-\d+$/)
442
+ end
443
+
436
444
  def parent_key project_config: @board.project_config
437
445
  # Although Atlassian is trying to standardize on one way to determine the parent, today it's a mess.
438
446
  # We try a variety of ways to get the parent and hopefully one of them will work. See this link:
@@ -454,8 +462,13 @@ class Issue
454
462
 
455
463
  custom_field_names&.each do |field_name|
456
464
  parent = fields[field_name]
457
- # A break would be more appropriate than a return but the runtime caused an error when we do that
458
- return parent if parent
465
+ next if parent.nil?
466
+ break if looks_like_issue_key? parent
467
+
468
+ project_config.file_system.log(
469
+ "Custom field #{field_name.inspect} should point to a parent id but found #{parent.inspect}"
470
+ )
471
+ parent = nil
459
472
  end
460
473
  end
461
474
 
@@ -505,6 +518,17 @@ class Issue
505
518
  result
506
519
  end
507
520
 
521
+ def done?
522
+ if artificial? || board.cycletime.nil?
523
+ # This was probably loaded as a linked issue, which means we don't know what board it really
524
+ # belonged to. The best we can do is look at the status category. This case should be rare but
525
+ # it can happen.
526
+ status.category_name == 'Done'
527
+ else
528
+ board.cycletime.done? self
529
+ end
530
+ end
531
+
508
532
  private
509
533
 
510
534
  def assemble_author raw
@@ -180,7 +180,7 @@ class ProjectConfig
180
180
 
181
181
  board_id = $1.to_i
182
182
  timezone_offset = exporter.timezone_offset
183
- JSON.parse(file_system.load("#{target_path}#{file}"))['values'].each do |json|
183
+ JSON.parse(file_system.load("#{target_path}#{file}"))['values']&.each do |json|
184
184
  @all_boards[board_id].sprints << Sprint.new(raw: json, timezone_offset: timezone_offset)
185
185
  end
186
186
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rules
4
- def ignore
5
- @ignore = true
4
+ def ignore value = true # rubocop:disable Style/OptionalBooleanParameter
5
+ @ignore = value
6
6
  end
7
7
 
8
8
  def ignored?
@@ -2,6 +2,8 @@
2
2
  "stalled_threshold_days": 5,
3
3
  "stalled_statuses": [],
4
4
 
5
- "blocked_link_text": [],
6
- "blocked_statuses": []
5
+ "blocked_link_text": ["is blocked by"],
6
+ "blocked_statuses": [],
7
+
8
+ "expedited_priority_names": ["Critical", "Highest"]
7
9
  }
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.4'
4
+ version: 2.5pre1
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-08-17 00:00:00.000000000 Z
11
+ date: 2024-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: random-word
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  requirements: []
156
- rubygems_version: 3.5.15
156
+ rubygems_version: 3.5.18
157
157
  signing_key:
158
158
  specification_version: 4
159
159
  summary: Extract Jira metrics