jirametrics 2.8 → 2.9.1pre1

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.
@@ -4,105 +4,106 @@ class StatusNotFoundError < StandardError
4
4
  end
5
5
 
6
6
  class StatusCollection
7
+ attr_reader :historical_status_mappings
8
+
7
9
  def initialize
8
10
  @list = []
11
+ @historical_status_mappings = {} # 'name:id' => category
9
12
  end
10
13
 
11
- def filter_status_names category_name:, including: nil, excluding: nil
12
- including = expand_statuses including
13
- excluding = expand_statuses excluding
14
+ # Return the status matching this id or nil if it can't be found.
15
+ def find_by_id id
16
+ @list.find { |status| status.id == id }
17
+ end
14
18
 
15
- @list.filter_map do |status|
16
- keep = status.category.name == category_name ||
17
- including.any? { |s| s.name == status.name }
18
- keep = false if excluding.any? { |s| s.name == status.name }
19
+ def find_all_by_name identifier
20
+ name, id = parse_name_id identifier
19
21
 
20
- status.name if keep
21
- end
22
- end
22
+ if id
23
+ status = find_by_id id
24
+ return [] if status.nil?
23
25
 
24
- def expand_statuses names_or_ids
25
- result = []
26
- return result if names_or_ids.nil?
27
-
28
- names_or_ids = [names_or_ids] unless names_or_ids.is_a? Array
29
-
30
- names_or_ids.each do |name_or_id|
31
- status = @list.find { |s| s.name == name_or_id || s.id == name_or_id }
32
- if status.nil?
33
- if block_given?
34
- yield name_or_id
35
- next
36
- else
37
- all_status_names = @list.collect { |s| "#{s.name.inspect}:#{s.id.inspect}" }.uniq.sort.join(', ')
38
- raise StatusNotFoundError, "Status not found: \"#{name_or_id}\". Possible statuses are: #{all_status_names}"
39
- end
26
+ if name && status.name != name
27
+ raise "Specified status ID of #{id} does not match specified name #{name.inspect}. " \
28
+ "You might have meant one of these: #{self}."
40
29
  end
41
-
42
- result << status
30
+ [status]
31
+ else
32
+ @list.select { |status| status.name == name }
43
33
  end
44
- result
45
34
  end
46
35
 
47
- def todo including: nil, excluding: nil
48
- filter_status_names category_name: 'To Do', including: including, excluding: excluding
36
+ def find_all_categories
37
+ @list
38
+ .collect(&:category)
39
+ .uniq
40
+ .sort_by(&:id)
49
41
  end
50
42
 
51
- def in_progress including: nil, excluding: nil
52
- filter_status_names category_name: 'In Progress', including: including, excluding: excluding
43
+ def parse_name_id name
44
+ # Names could arrive in one of the following formats: "Done:3", "3", "Done"
45
+ if name =~ /^(.*):(\d+)$/
46
+ [$1, $2.to_i]
47
+ elsif name.match?(/^\d+$/)
48
+ [nil, name.to_i]
49
+ else
50
+ [name, nil]
51
+ end
53
52
  end
54
53
 
55
- def done including: nil, excluding: nil
56
- filter_status_names category_name: 'Done', including: including, excluding: excluding
57
- end
54
+ def find_all_categories_by_name identifier
55
+ key = nil
56
+ id = nil
58
57
 
59
- # Return the status matching this id or nil if it can't be found.
60
- def find_by_id id
61
- @list.find { |status| status.id == id }
58
+ if identifier.is_a? Symbol
59
+ key = identifier.to_s
60
+ else
61
+ name, id = parse_name_id identifier
62
+ end
63
+
64
+ find_all_categories.select { |c| c.id == id || c.name == name || c.key == key }
62
65
  end
63
66
 
64
- def find_all_by_name name
65
- @list.select { |status| status.name == name }
67
+ def collect(&block) = @list.collect(&block)
68
+ def find(&block) = @list.find(&block)
69
+ def each(&block) = @list.each(&block)
70
+ def select(&block) = @list.select(&block)
71
+ def <<(arg) = @list << arg
72
+ def empty? = @list.empty?
73
+ def clear = @list.clear
74
+ def delete(object) = @list.delete(object)
75
+
76
+ def to_s
77
+ "[#{@list.sort.join(', ')}]"
66
78
  end
67
79
 
68
- def find_category_by_name name
69
- category = @list.find { |status| status.category.name == name }&.category
70
- unless category
71
- set = Set.new
72
- @list.each do |status|
73
- set << status.category.to_s
74
- end
75
- raise "Unable to find status category #{name.inspect} in [#{set.to_a.sort.join(', ')}]"
76
- end
77
- category
80
+ def inspect
81
+ "StatusCollection#{self}"
78
82
  end
79
83
 
80
- # This is used to create a status that was found in the history but has since been deleted.
81
84
  def fabricate_status_for id:, name:
82
- first_in_progress_status = @list.find { |s| s.category.indeterminate? }
83
- raise "Can't find even one in-progress status in [#{set.to_a.sort.join(', ')}]" unless first_in_progress_status
85
+ category = @historical_status_mappings["#{name.inspect}:#{id.inspect}"]
86
+ category = in_progress_category if category.nil?
84
87
 
85
88
  status = Status.new(
86
89
  name: name,
87
90
  id: id,
88
- category_name: first_in_progress_status.category.name,
89
- category_id: first_in_progress_status.category.id,
90
- category_key: first_in_progress_status.category.key
91
+ category_name: category.name,
92
+ category_id: category.id,
93
+ category_key: category.key,
94
+ artificial: true
91
95
  )
92
- self << status
96
+ @list << status
93
97
  status
94
98
  end
95
99
 
96
- def collect(&block) = @list.collect(&block)
97
- def find(&block) = @list.find(&block)
98
- def each(&block) = @list.each(&block)
99
- def select(&block) = @list.select(&block)
100
- def <<(arg) = @list << arg
101
- def empty? = @list.empty?
102
- def clear = @list.clear
103
- def delete(object) = @list.delete(object)
100
+ private
104
101
 
105
- def inspect
106
- "StatusCollection(#{@list.join(', ')})"
102
+ # Return the in-progress category or raise an error if we can't find one.
103
+ def in_progress_category
104
+ first_in_progress_status = find { |s| s.category.indeterminate? }
105
+ raise "Can't find even one in-progress status in #{self}" unless first_in_progress_status
106
+
107
+ first_in_progress_status.category
107
108
  end
108
109
  end
@@ -9,9 +9,9 @@ module ValueEquality
9
9
  names = object.instance_variables
10
10
  if object.respond_to? :value_equality_ignored_variables
11
11
  ignored_variables = object.value_equality_ignored_variables
12
- names.reject! { |n| ignored_variables.include? n }
12
+ names.reject! { |n| ignored_variables.include? n.to_sym }
13
13
  end
14
- names.map { |variable| instance_variable_get variable }
14
+ names.map { |variable| object.instance_variable_get variable }
15
15
  end
16
16
 
17
17
  code.call(self) == code.call(other)
data/lib/jirametrics.rb CHANGED
@@ -68,7 +68,6 @@ class JiraMetrics < Thor
68
68
  require 'jirametrics/grouping_rules'
69
69
  require 'jirametrics/daily_wip_chart'
70
70
  require 'jirametrics/groupable_issue_chart'
71
- require 'jirametrics/discard_changes_before'
72
71
  require 'jirametrics/css_variable'
73
72
 
74
73
  require 'jirametrics/aggregate_config'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirametrics
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.8'
4
+ version: 2.9.1pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-30 00:00:00.000000000 Z
10
+ date: 2025-01-21 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: random-word
@@ -52,8 +51,7 @@ dependencies:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
53
  version: 1.2.2
55
- description: Tool to extract metrics from Jira and export to either a report or to
56
- CSV files
54
+ description: Extract metrics from Jira and export to either a report or to CSV files
57
55
  email: mbowler@gargoylesoftware.com
58
56
  executables:
59
57
  - jirametrics
@@ -84,7 +82,6 @@ files:
84
82
  - lib/jirametrics/daily_wip_chart.rb
85
83
  - lib/jirametrics/data_quality_report.rb
86
84
  - lib/jirametrics/dependency_chart.rb
87
- - lib/jirametrics/discard_changes_before.rb
88
85
  - lib/jirametrics/download_config.rb
89
86
  - lib/jirametrics/downloader.rb
90
87
  - lib/jirametrics/estimate_accuracy_chart.rb
@@ -139,7 +136,6 @@ metadata:
139
136
  bug_tracker_uri: https://github.com/mikebowler/jirametrics/issues
140
137
  changelog_uri: https://jirametrics.org/changes
141
138
  documentation_uri: https://jirametrics.org
142
- post_install_message:
143
139
  rdoc_options: []
144
140
  require_paths:
145
141
  - lib
@@ -154,8 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
150
  - !ruby/object:Gem::Version
155
151
  version: '0'
156
152
  requirements: []
157
- rubygems_version: 3.5.21
158
- signing_key:
153
+ rubygems_version: 3.6.2
159
154
  specification_version: 4
160
155
  summary: Extract Jira metrics
161
156
  test_files: []
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DiscardChangesBefore
4
- def discard_changes_before status_becomes: nil, &block
5
- if status_becomes
6
- status_becomes = [status_becomes] unless status_becomes.is_a? Array
7
-
8
- block = lambda do |issue|
9
- trigger_statuses = status_becomes.collect do |status_name|
10
- if status_name == :backlog
11
- issue.board.backlog_statuses.collect(&:name)
12
- else
13
- status_name
14
- end
15
- end.flatten
16
-
17
- time = nil
18
- issue.changes.each do |change|
19
- time = change.time if change.status? && trigger_statuses.include?(change.value) && change.artificial? == false
20
- end
21
- time
22
- end
23
- end
24
-
25
- issues_cutoff_times = []
26
- issues.each do |issue|
27
- cutoff_time = block.call(issue)
28
- issues_cutoff_times << [issue, cutoff_time] if cutoff_time
29
- end
30
-
31
- discard_changes_before_hook issues_cutoff_times
32
-
33
- issues_cutoff_times.each do |issue, cutoff_time|
34
- issue.discard_changes_before cutoff_time
35
- end
36
- end
37
- end