jirametrics 2.4 → 2.5pre1

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: 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