jirametrics 2.25.1 → 2.26

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: a93af46dd816e8ff88d28bcff6f3576854a3d20f15ea23496797bec53b4f615d
4
- data.tar.gz: a1ffd32cad9ff93ad61b2e72451cd8aa48e7e73785ac98ea4e6018d533b203ee
3
+ metadata.gz: b7ae2f8704310fc2fcea52150053a46c0b42cfa70f1a70c3852ec1ed70f7a2ae
4
+ data.tar.gz: 60401ec8596603ee4ba7d6491286a577ea51a3a888d8cc9ea1de5ee7c281dbe7
5
5
  SHA512:
6
- metadata.gz: 50468c878959ce6508d8470195c2c4d6f8e98bc0a9bebafaaceceb9e296d8f3bdbf194e36a00459c0c34f9174371ccb53ac1dc02b8cf1a8fccb3d6a3ee143c45
7
- data.tar.gz: 9c4c6ef0c5c70d417d5c305a311d25d292bbcea1bcd5777fdf004922178a255aa65180fb0b4455eda8f9d9a72f5f1c23ac650c2e83368c8fc5926ba6aefc0a1f
6
+ metadata.gz: 4b4cd517bb7be4fc1e660b7d3950dfc1ed48125c5fa7334b5d19c115c85a29915fbbf0c02f142a79f68a401a86913058b84e3dfb00dd5ae9d7c39986c3ff6bb3
7
+ data.tar.gz: 6200ef8b7ab73aaad69986fb1f38c1bc7df2ca280c3c8559fc83093276b2b7c628bb91b3d7850464003c01ced5eea839cde155688e1f1cf1e0a42b533bc09244
@@ -15,7 +15,7 @@ class AgingWorkBarChart < ChartBase
15
15
  newest at the bottom.
16
16
  </p>
17
17
  <p>
18
- There are <%= current_board.scrum? ? 'four' : 'three' %> bars for each issue, and hovering over any of the bars will provide more details.
18
+ There are <%= (current_board.scrum? || aggregated_project?) ? 'four' : 'three' %> bars for each issue, and hovering over any of the bars will provide more details.
19
19
  <ol>
20
20
  <li>Status: The status the issue was in at any time. The colour indicates the
21
21
  status category, which will be one of #{color_block '--status-category-todo-color'} To Do,
@@ -25,7 +25,7 @@ class AgingWorkBarChart < ChartBase
25
25
  or #{color_block '--stalled-color'} stalled.</li>
26
26
  <li>Priority: This shows the priority over time. If one of these priorities is considered expedited
27
27
  then it will be drawn with diagonal lines.</li>
28
- <% if current_board.scrum? %>
28
+ <% if current_board.scrum? || aggregated_project? %>
29
29
  <li>Sprints: The sprints that the issue was in.</li>
30
30
  <% end %>
31
31
  </ol>
@@ -84,7 +84,7 @@ class AgingWorkBarChart < ChartBase
84
84
  ['blocked', collect_blocked_stalled_ranges(issue: issue, issue_start_time: issue_start_time)],
85
85
  ['priority', collect_priority_ranges(issue: issue)]
86
86
  ]
87
- bar_data << ['sprints', collect_sprint_ranges(issue: issue)] if current_board.scrum?
87
+ bar_data << ['sprints', collect_sprint_ranges(issue: issue)] if current_board.scrum? || aggregated_project?
88
88
 
89
89
  bar_data.each { |entry| clip_ranges_to_start_time(ranges: entry.last, issue_start_time: issue_start_time) }
90
90
 
@@ -113,7 +113,7 @@ class AgingWorkBarChart < ChartBase
113
113
  def grow_chart_height_if_too_many_issues aging_issue_count:
114
114
  px_per_bar = 10
115
115
  bars_per_issue = 3
116
- bars_per_issue += 1 if current_board.scrum?
116
+ bars_per_issue += 1 if current_board.scrum? || aggregated_project?
117
117
 
118
118
  preferred_height = aging_issue_count * px_per_bar * bars_per_issue
119
119
  @canvas_height = preferred_height if @canvas_height.nil? || @canvas_height < preferred_height
@@ -31,12 +31,17 @@ class CfdDataBuilder
31
31
 
32
32
  # Returns { hwm_timeline: [[date, hwm_value], ...], correction_windows: [...] }
33
33
  def process_issue issue, column_map
34
+ start_time = issue.started_stopped_times.first
35
+ return { hwm_timeline: [], correction_windows: [] } if start_time.nil?
36
+
34
37
  high_water_mark = nil
35
38
  correction_open_since = nil
36
39
  correction_windows = []
37
40
  hwm_timeline = [] # sorted chronologically by date
38
41
 
39
42
  issue.status_changes.each do |change|
43
+ next if change.time < start_time
44
+
40
45
  col_index = column_map[change.value_id]
41
46
  next if col_index.nil?
42
47
 
@@ -35,6 +35,10 @@ class CycletimeScatterplot < TimeBasedScatterplot
35
35
  end
36
36
  end
37
37
 
38
+ def minimum_y_value
39
+ 1 # Values under 1 day are data quality problems; they're flagged in the quality report instead
40
+ end
41
+
38
42
  def all_items
39
43
  completed_issues_in_range include_unstarted: false
40
44
  end
@@ -87,6 +87,23 @@ class Downloader
87
87
  @file_system.log text, also_write_to_stderr: both
88
88
  end
89
89
 
90
+ def log_start text
91
+ @file_system.log_start text
92
+ end
93
+
94
+ def start_progress
95
+ @file_system.start_progress
96
+ end
97
+
98
+ def progress_dot message = nil
99
+ @file_system.log message if message
100
+ @file_system.progress_dot
101
+ end
102
+
103
+ def end_progress
104
+ @file_system.end_progress
105
+ end
106
+
90
107
  def find_board_ids
91
108
  ids = @download_config.project_config.board_configs.collect(&:id)
92
109
  raise 'Board ids must be specified' if ids.empty?
@@ -233,13 +250,6 @@ class Downloader
233
250
  @metadata[key] = value
234
251
  end
235
252
 
236
- # If rolling_date_count has changed, we may be missing data outside the previous range,
237
- # so force a full re-download.
238
- if @metadata['rolling_date_count'] != @download_config.rolling_date_count
239
- log ' rolling_date_count has changed. Forcing a full download.', both: true
240
- @cached_data_format_is_current = false
241
- @metadata = {}
242
- end
243
253
  end
244
254
 
245
255
  # Even if this is the old format, we want to obey this one tag
@@ -53,6 +53,7 @@ class DownloaderForCloud < Downloader
53
53
  next_page_token = nil
54
54
  issue_count = 0
55
55
 
56
+ start_progress
56
57
  loop do
57
58
  relative_url = +''
58
59
  relative_url << '/rest/api/3/search/jql'
@@ -75,11 +76,12 @@ class DownloaderForCloud < Downloader
75
76
  issue_count += 1
76
77
  end
77
78
 
78
- message = " Found #{issue_count} issues"
79
- log message, both: true
79
+ progress_dot " Found #{issue_count} issues"
80
80
 
81
81
  break unless next_page_token
82
82
  end
83
+ end_progress
84
+
83
85
  hash
84
86
  end
85
87
 
@@ -88,7 +90,7 @@ class DownloaderForCloud < Downloader
88
90
  # that only returns the "recent" changes, not all of them. So now we get the issue
89
91
  # without changes and then make a second call for that changes. Then we insert it
90
92
  # into the raw issue as if it had been there all along.
91
- log " Downloading #{issue_datas.size} issues", both: true
93
+ log " Downloading #{issue_datas.size} issues"
92
94
  payload = {
93
95
  'fields' => ['*all'],
94
96
  'issueIdsOrKeys' => issue_datas.collect(&:key)
@@ -168,13 +170,12 @@ class DownloaderForCloud < Downloader
168
170
 
169
171
  loop do
170
172
  related_issue_keys = Set.new
171
- issue_data_hash
172
- .values
173
- .reject { |data| data.up_to_date }
174
- .each_slice(100) do |slice|
175
- slice = bulk_fetch_issues(
176
- issue_datas: slice, board: board, in_initial_query: true
177
- )
173
+ stale = issue_data_hash.values.reject { |data| data.up_to_date }
174
+ unless stale.empty?
175
+ log_start ' Downloading more issues '
176
+ stale.each_slice(100) do |slice|
177
+ slice = bulk_fetch_issues(issue_datas: slice, board: board, in_initial_query: true)
178
+ progress_dot
178
179
  slice.each do |data|
179
180
  @file_system.save_json(
180
181
  json: data.issue.raw, filename: data.cache_path
@@ -195,6 +196,8 @@ class DownloaderForCloud < Downloader
195
196
  end
196
197
  end
197
198
  end
199
+ end_progress
200
+ end
198
201
 
199
202
  # Remove all the ones we already downloaded
200
203
  related_issue_keys.reject! { |key| issue_data_hash[key] }
@@ -68,6 +68,28 @@ class FileSystem
68
68
  $stderr.puts message # rubocop:disable Style/StderrPuts
69
69
  end
70
70
 
71
+ def log_start message
72
+ logfile.puts message
73
+ return if logfile == $stdout
74
+
75
+ $stderr.print message
76
+ $stderr.flush
77
+ end
78
+
79
+ def start_progress
80
+ $stderr.print ' '
81
+ $stderr.flush
82
+ end
83
+
84
+ def progress_dot
85
+ $stderr.print '.'
86
+ $stderr.flush
87
+ end
88
+
89
+ def end_progress
90
+ $stderr.puts '' # rubocop:disable Style/StderrPuts
91
+ end
92
+
71
93
  # In some Jira instances, a sizeable portion of the JSON is made up of empty fields. I've seen
72
94
  # cases where this simple compression will drop the filesize by half.
73
95
  def compress node
@@ -17,6 +17,7 @@ module GroupableIssueChart
17
17
  result = {}
18
18
  ignored_issues = []
19
19
  @issue_hints = {}
20
+ @issue_periods = {}
20
21
  completed_issues.each do |issue|
21
22
  rules = GroupingRules.new
22
23
  @group_by_block.call(issue, rules)
@@ -26,6 +27,7 @@ module GroupableIssueChart
26
27
  end
27
28
 
28
29
  @issue_hints[issue] = rules.issue_hint
30
+ @issue_periods[issue] = rules.last_day_of_period
29
31
  (result[rules] ||= []) << issue
30
32
  end
31
33
 
@@ -2,7 +2,11 @@
2
2
 
3
3
  class GroupingRules < Rules
4
4
  attr_accessor :label, :issue_hint, :label_hint
5
- attr_reader :color
5
+ attr_reader :color, :last_day_of_period
6
+
7
+ def last_day_of_period= value
8
+ @last_day_of_period = value.is_a?(String) ? Date.parse(value) : value
9
+ end
6
10
 
7
11
  def eql? other
8
12
  other.label == @label && other.color == @color
@@ -5,6 +5,7 @@ class HtmlGenerator
5
5
 
6
6
  def create_html output_filename:, settings:, project_name: ''
7
7
  @settings = settings
8
+ project_name = project_name.to_s
8
9
  html_directory = "#{Pathname.new(File.realpath(__FILE__)).dirname}/html"
9
10
  css = load_css html_directory: html_directory
10
11
  javascript = file_system.load(File.join(html_directory, 'index.js'))
@@ -598,6 +598,16 @@ class ProjectConfig
598
598
  if status_becomes
599
599
  status_becomes = [status_becomes] unless status_becomes.is_a? Array
600
600
 
601
+ status_becomes.each do |status_name|
602
+ next if status_name == :backlog
603
+
604
+ found = possible_statuses.find_all_by_name status_name
605
+ if found.empty?
606
+ raise "discard_changes_before: Status #{status_name.inspect} not found. " \
607
+ "Possible statuses are: #{possible_statuses}"
608
+ end
609
+ end
610
+
601
611
  block = lambda do |issue|
602
612
  trigger_statuses = status_becomes.collect do |status_name|
603
613
  if status_name == :backlog
@@ -79,12 +79,21 @@ class ThroughputChart < ChartBase
79
79
  end
80
80
  end
81
81
 
82
+ def calculate_custom_periods
83
+ last_days = @issue_periods.values.compact.uniq.sort
84
+ last_days.each_with_index.map do |last_day, i|
85
+ first_day = i.zero? ? @date_range.begin : last_days[i - 1] + 1
86
+ first_day..last_day
87
+ end
88
+ end
89
+
82
90
  def weekly_throughput_dataset completed_issues:, label:, color:, dashed: false, label_hint: nil
91
+ periods = @issue_periods&.values&.any? ? calculate_custom_periods : calculate_time_periods
83
92
  result = {
84
93
  label: label,
85
94
  label_hint: label_hint,
86
95
  data: throughput_dataset(
87
- periods: calculate_time_periods, completed_issues: completed_issues, label_hint: label_hint
96
+ periods: periods, completed_issues: completed_issues, label_hint: label_hint
88
97
  ),
89
98
  fill: false,
90
99
  showLine: true,
@@ -109,10 +118,17 @@ class ThroughputChart < ChartBase
109
118
  end
110
119
 
111
120
  def throughput_dataset periods:, completed_issues:, label_hint: nil
121
+ custom_mode = @issue_periods&.values&.any?
112
122
  periods.collect do |period|
113
123
  closed_issues = completed_issues.filter_map do |issue|
114
124
  stop_date = issue.started_stopped_dates.last
115
- [stop_date, issue] if stop_date && period.include?(stop_date)
125
+ next unless stop_date
126
+
127
+ if custom_mode
128
+ [stop_date, issue] if @issue_periods[issue] == period.end
129
+ elsif period.include?(stop_date)
130
+ [stop_date, issue]
131
+ end
116
132
  end
117
133
 
118
134
  date_label = "on #{period.end}"
@@ -79,9 +79,14 @@ class TimeBasedScatterplot < ChartBase
79
79
  }
80
80
  end
81
81
 
82
+ def minimum_y_value
83
+ nil
84
+ end
85
+
82
86
  def data_for_item item, rules: nil
83
87
  y = y_value(item)
84
- return nil if y < 1 # These will get called out on the quality report
88
+ min = minimum_y_value
89
+ return nil if min && y < min
85
90
 
86
91
  @highest_y_value = y if @highest_y_value < y
87
92
 
@@ -93,7 +98,9 @@ class TimeBasedScatterplot < ChartBase
93
98
  end
94
99
 
95
100
  def calculate_percent_line items
101
+ min = minimum_y_value
96
102
  times = items.collect { |item| y_value(item) }
103
+ times.reject! { |y| min && y < min }
97
104
  index = times.size * 85 / 100
98
105
  times.sort[index]
99
106
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jirametrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.25.1
4
+ version: '2.26'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bowler