jirametrics 2.2.1 → 2.3

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +13 -25
  3. data/lib/jirametrics/aging_work_bar_chart.rb +57 -39
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +1 -1
  5. data/lib/jirametrics/aging_work_table.rb +9 -26
  6. data/lib/jirametrics/board_config.rb +2 -2
  7. data/lib/jirametrics/chart_base.rb +27 -39
  8. data/lib/jirametrics/cycletime_histogram.rb +1 -1
  9. data/lib/jirametrics/cycletime_scatterplot.rb +1 -1
  10. data/lib/jirametrics/daily_wip_by_age_chart.rb +1 -1
  11. data/lib/jirametrics/daily_wip_chart.rb +1 -13
  12. data/lib/jirametrics/dependency_chart.rb +1 -1
  13. data/lib/jirametrics/{story_point_accuracy_chart.rb → estimate_accuracy_chart.rb} +31 -25
  14. data/lib/jirametrics/examples/standard_project.rb +1 -1
  15. data/lib/jirametrics/expedited_chart.rb +3 -1
  16. data/lib/jirametrics/exporter.rb +2 -2
  17. data/lib/jirametrics/file_config.rb +5 -7
  18. data/lib/jirametrics/file_system.rb +11 -2
  19. data/lib/jirametrics/groupable_issue_chart.rb +2 -4
  20. data/lib/jirametrics/hierarchy_table.rb +4 -4
  21. data/lib/jirametrics/html/aging_work_table.erb +3 -3
  22. data/lib/jirametrics/html_report_config.rb +61 -74
  23. data/lib/jirametrics/issue.rb +70 -39
  24. data/lib/jirametrics/project_config.rb +12 -6
  25. data/lib/jirametrics/sprint_burndown.rb +11 -0
  26. data/lib/jirametrics/status_collection.rb +4 -1
  27. data/lib/jirametrics/throughput_chart.rb +1 -1
  28. data/lib/jirametrics.rb +1 -1
  29. metadata +5 -7
  30. data/lib/jirametrics/experimental/generator.rb +0 -210
  31. data/lib/jirametrics/experimental/info.rb +0 -77
  32. /data/lib/jirametrics/html/{story_point_accuracy_chart.erb → estimate_accuracy_chart.erb} +0 -0
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'random-word'
4
- require 'require_all'
5
- require_all 'lib'
6
-
7
- def to_time date
8
- Time.new date.year, date.month, date.day, rand(0..23), rand(0..59), rand(0..59)
9
- end
10
-
11
- class FakeIssue
12
- @@issue_number = 1
13
- attr_reader :effort, :raw, :worker
14
-
15
- def initialize date:, type:, worker:
16
- @raw = {
17
- key: "FAKE-#{@@issue_number += 1}",
18
- changelog: {
19
- histories: []
20
- },
21
- fields: {
22
- created: to_time(date),
23
- updated: to_time(date),
24
- creator: {
25
- displayName: 'George Jetson'
26
- },
27
- issuetype: {
28
- name: type
29
- },
30
- status: {
31
- name: 'To Do',
32
- id: 1,
33
- statusCategory: {
34
- id: 2,
35
- name: 'To Do'
36
- }
37
- },
38
- priority: {
39
- name: ''
40
- },
41
- summary: RandomWord.phrases.next.gsub(_, ' '),
42
- issuelinks: [],
43
- fixVersions: []
44
- }
45
- }
46
-
47
- @workers = [worker]
48
- @effort = case type
49
- when 'Story'
50
- [1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 6].sample
51
- else
52
- [1, 2, 3].sample
53
- end
54
- unblock
55
- @done = false
56
- @last_status = 'To Do'
57
- @last_status_id = 1
58
- change_status new_status: 'In Progress', new_status_id: 3, date: date
59
- end
60
-
61
- def blocked? = @blocked
62
- def block = @blocked = true
63
- def unblock = @blocked = false
64
-
65
- def key = @raw[:key]
66
-
67
- def do_work date:, effort:
68
- raise 'Already done' if done?
69
-
70
- @effort -= effort
71
- return unless done?
72
-
73
- change_status new_status: 'Done', new_status_id: 5, date: date
74
- # fix_change_timestamps
75
- end
76
-
77
- def fix_change_timestamps
78
- # since the timestamps have random hours, it's possible for them to be issued out of order. Sort them now
79
- changes = @raw[:changelog][:histories]
80
- times = [@raw[:fields][:created]] + changes.collect { |change| change[:created] }
81
- times.sort!
82
-
83
- @raw[:fields][:created] = times.shift
84
- @raw[:fields][:updated] = times[-1]
85
- changes.each do |change|
86
- change[:created] = times.shift
87
- end
88
- end
89
-
90
- def done? = @effort <= 0
91
-
92
- def change_status date:, new_status:, new_status_id:
93
- @raw[:changelog][:histories] << {
94
- author: {
95
- emailAddress: 'george@jetson.com',
96
- displayName: 'George Jetson'
97
- },
98
- created: to_time(date),
99
- items: [
100
- {
101
- field: 'status',
102
- fieldtype: 'jira',
103
- fieldId: 'status',
104
- from: @last_status_id,
105
- fromString: @last_status,
106
- to: new_status_id,
107
- toString: new_status
108
- }
109
- ]
110
- }
111
-
112
- @last_status = new_status
113
- @last_status_id = new_status_id
114
- end
115
- end
116
-
117
- class Worker
118
- attr_accessor :issue
119
- end
120
-
121
- class Generator
122
- def initialize
123
- @random = Random.new
124
- @file_prefix = 'fake'
125
- @target_path = 'target/'
126
-
127
- # @probability_work_will_be_pushed = 20
128
- @probability_unblocked_work_becomes_blocked = 20
129
- @probability_blocked_work_becomes_unblocked = 20
130
- @date_range = (Date.today - 500)..Date.today
131
- @issues = []
132
- @workers = []
133
- 5.times { @workers << Worker.new }
134
- end
135
-
136
- def run
137
- remove_old_files
138
- @date_range.each_with_index do |date, day|
139
- yield date, day if block_given?
140
- process_date(date, day) if (1..5).cover? date.wday # Weekday
141
- end
142
-
143
- @issues.each do |issue|
144
- issue.fix_change_timestamps
145
- File.open "target/fake_issues/#{issue.key}.json", 'w' do |file|
146
- file.puts JSON.pretty_generate(issue.raw)
147
- end
148
- end
149
-
150
- File.write 'target/fake_meta.json', JSON.pretty_generate({
151
- time_start: (@date_range.end - 90).to_time,
152
- time_end: @date_range.end.to_time,
153
- 'no-download': true
154
- })
155
- puts "Created #{@issues.size} fake issues"
156
- end
157
-
158
- def remove_old_files
159
- path = "#{@target_path}#{@file_prefix}_issues"
160
- Dir.foreach path do |file|
161
- next unless file.match?(/-\d+\.json$/)
162
-
163
- filename = "#{path}/#{file}"
164
- File.unlink filename
165
- end
166
- end
167
-
168
- def lucky? probability
169
- @random.rand(1..100) <= probability
170
- end
171
-
172
- def next_issue_for worker:, date:, type:
173
- # First look for something I already started
174
- issue = @issues.find { |i| i.worker == worker && !i.done? && !i.blocked? }
175
-
176
- # Then look for something that someone else started
177
- issue = @issues.find { |i| i.worker != worker && !i.done? && !i.blocked? } if issue.nil? && lucky?(40)
178
-
179
- # Then start new work
180
- issue = FakeIssue.new(date: date, type: type, worker: worker) if issue.nil?
181
-
182
- issue
183
- end
184
-
185
- def process_date date, _simulation_day
186
- @issues.each do |issue|
187
- if issue.blocked?
188
- issue.unblock if lucky? @probability_blocked_work_becomes_unblocked
189
- elsif lucky? @probability_unblocked_work_becomes_blocked
190
- issue.block
191
- end
192
- end
193
-
194
- possible_capacities = [0, 1, 1, 1, 2]
195
- @workers.each do |worker|
196
- worker_capacity = possible_capacities.sample
197
- if worker.issue.nil? || worker.issue.done?
198
- type = lucky?(89) ? 'Story' : 'Bug'
199
- worker.issue = next_issue_for worker: worker, date: date, type: type
200
- @issues << worker.issue
201
- end
202
-
203
- worker.issue = next_issue_for worker: worker, date: date, type: type if worker.issue.blocked?
204
- worker.issue.do_work date: date, effort: worker_capacity
205
- worker.issue = nil if worker.issue.done?
206
- end
207
- end
208
- end
209
-
210
- Generator.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'require_all'
4
- require_all 'lib'
5
-
6
- class InfoDumper
7
- def initialize
8
- @target_dir = 'target/'
9
- end
10
-
11
- def run key
12
- find_file_prefixes.each do |prefix|
13
- path = "#{@target_dir}#{prefix}_issues/#{key}.json"
14
- path = "#{@target_dir}#{prefix}_issues"
15
- Dir.foreach path do |file|
16
- if file.match?(/^#{key}.+\.json$/)
17
- issue = Issue.new raw: JSON.parse(File.read(File.join(path, file))), board: nil
18
- dump issue
19
- end
20
- end
21
- end
22
- end
23
-
24
- def find_file_prefixes
25
- prefixes = []
26
- Dir.foreach @target_dir do |file|
27
- prefixes << $1 if file =~ /^(.+)_issues$/
28
- end
29
- prefixes
30
- end
31
-
32
- def dump issue
33
- puts "#{issue.key} (#{issue.type}): #{compact_text issue.summary, 200}"
34
-
35
- assignee = issue.raw['fields']['assignee']
36
- puts " [assignee] #{assignee['name'].inspect} <#{assignee['emailAddress']}>" unless assignee.nil?
37
-
38
- issue.raw['fields']['issuelinks'].each do |link|
39
- puts " [link] #{link['type']['outward']} #{link['outwardIssue']['key']}" if link['outwardIssue']
40
- puts " [link] #{link['type']['inward']} #{link['inwardIssue']['key']}" if link['inwardIssue']
41
- end
42
- issue.changes.each do |change|
43
- value = change.value
44
- old_value = change.old_value
45
-
46
- # Description fields get pretty verbose so reduce the clutter
47
- if change.field == 'description' || change.field == 'summary'
48
- value = compact_text value
49
- old_value = compact_text old_value
50
- end
51
-
52
- author = change.author
53
- author = "(#{author})" if author
54
- message = " [change] #{change.time} [#{change.field}] "
55
- message << "#{compact_text(old_value).inspect} -> " unless old_value.nil? || old_value.empty?
56
- message << compact_text(value).inspect
57
- message << " #{author}" if author
58
- message << ' <<artificial entry>>' if change.artificial?
59
- puts message
60
- end
61
- puts ''
62
- end
63
-
64
- def compact_text text, max = 60
65
- return nil if text.nil?
66
-
67
- text = text.gsub(/\s+/, ' ').strip
68
- text = "#{text[0..max]}..." if text.length > max
69
- text
70
- end
71
- end
72
-
73
- if __FILE__ == $PROGRAM_NAME
74
- ARGV.each do |key|
75
- InfoDumper.new.run key
76
- end
77
- end