jirametrics 2.2.1 → 2.3

Sign up to get free protection for your applications and to get access to all the features.
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