jirametrics 2.13 → 2.22
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 +4 -4
- data/lib/jirametrics/aging_work_bar_chart.rb +176 -134
- data/lib/jirametrics/anonymizer.rb +8 -6
- data/lib/jirametrics/atlassian_document_format.rb +8 -4
- data/lib/jirametrics/bar_chart_range.rb +17 -0
- data/lib/jirametrics/board.rb +4 -0
- data/lib/jirametrics/board_config.rb +4 -1
- data/lib/jirametrics/change_item.rb +11 -4
- data/lib/jirametrics/chart_base.rb +36 -2
- data/lib/jirametrics/cycletime_config.rb +22 -4
- data/lib/jirametrics/cycletime_histogram.rb +3 -1
- data/lib/jirametrics/cycletime_scatterplot.rb +36 -17
- data/lib/jirametrics/daily_view.rb +49 -42
- data/lib/jirametrics/daily_wip_by_age_chart.rb +3 -4
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +13 -3
- data/lib/jirametrics/daily_wip_chart.rb +1 -1
- data/lib/jirametrics/data_quality_report.rb +8 -3
- data/lib/jirametrics/dependency_chart.rb +4 -1
- data/lib/jirametrics/downloader.rb +34 -99
- data/lib/jirametrics/downloader_for_cloud.rb +202 -0
- data/lib/jirametrics/downloader_for_data_center.rb +94 -0
- data/lib/jirametrics/examples/standard_project.rb +9 -9
- data/lib/jirametrics/expedited_chart.rb +1 -1
- data/lib/jirametrics/exporter.rb +12 -5
- data/lib/jirametrics/file_system.rb +24 -1
- data/lib/jirametrics/fix_version.rb +13 -0
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +1 -1
- data/lib/jirametrics/groupable_issue_chart.rb +7 -1
- data/lib/jirametrics/html/aging_work_bar_chart.erb +2 -1
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +3 -1
- data/lib/jirametrics/html/aging_work_table.erb +2 -0
- data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
- data/lib/jirametrics/html/cycletime_histogram.erb +4 -2
- data/lib/jirametrics/html/cycletime_scatterplot.erb +6 -6
- data/lib/jirametrics/html/daily_wip_chart.erb +2 -0
- data/lib/jirametrics/html/estimate_accuracy_chart.erb +2 -0
- data/lib/jirametrics/html/expedited_chart.erb +3 -1
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +2 -0
- data/lib/jirametrics/html/index.css +21 -9
- data/lib/jirametrics/html/index.erb +3 -35
- data/lib/jirametrics/html/index.js +114 -0
- data/lib/jirametrics/html/sprint_burndown.erb +11 -3
- data/lib/jirametrics/html/throughput_chart.erb +2 -2
- data/lib/jirametrics/html_generator.rb +31 -0
- data/lib/jirametrics/html_report_config.rb +8 -25
- data/lib/jirametrics/issue.rb +127 -22
- data/lib/jirametrics/jira_gateway.rb +55 -17
- data/lib/jirametrics/project_config.rb +42 -5
- data/lib/jirametrics/raw_javascript.rb +13 -0
- data/lib/jirametrics/settings.json +3 -1
- data/lib/jirametrics/sprint.rb +12 -0
- data/lib/jirametrics/sprint_burndown.rb +6 -2
- data/lib/jirametrics/stitcher.rb +75 -0
- data/lib/jirametrics.rb +26 -70
- metadata +10 -3
|
@@ -3,21 +3,61 @@
|
|
|
3
3
|
require 'cgi'
|
|
4
4
|
require 'json'
|
|
5
5
|
require 'English'
|
|
6
|
+
require 'open3'
|
|
6
7
|
|
|
7
8
|
class JiraGateway
|
|
8
|
-
attr_accessor :ignore_ssl_errors
|
|
9
|
+
attr_accessor :ignore_ssl_errors
|
|
10
|
+
attr_reader :jira_url, :settings, :file_system
|
|
9
11
|
|
|
10
|
-
def initialize file_system:
|
|
12
|
+
def initialize file_system:, jira_config:, settings:
|
|
11
13
|
@file_system = file_system
|
|
14
|
+
load_jira_config(jira_config)
|
|
15
|
+
@settings = settings
|
|
16
|
+
@ignore_ssl_errors = settings['ignore_ssl_errors']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def post_request relative_url:, payload:
|
|
20
|
+
command = make_curl_command url: "#{@jira_url}#{relative_url}", method: 'POST'
|
|
21
|
+
exec_and_parse_response command: command, stdin_data: payload
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def exec_and_parse_response command:, stdin_data:
|
|
25
|
+
log_entry = " #{command.gsub(/\s+/, ' ')}"
|
|
26
|
+
log_entry = sanitize_message log_entry
|
|
27
|
+
@file_system.log log_entry
|
|
28
|
+
|
|
29
|
+
stdout, stderr, status = capture3(command, stdin_data: stdin_data)
|
|
30
|
+
unless status.success?
|
|
31
|
+
@file_system.log "Failed call with exit status #{status.exitstatus}!"
|
|
32
|
+
@file_system.log "Returned (stdout): #{stdout.inspect}"
|
|
33
|
+
@file_system.log "Returned (stderr): #{stderr.inspect}"
|
|
34
|
+
raise "Failed call with exit status #{status.exitstatus}. " \
|
|
35
|
+
"See #{@file_system.logfile_name} for details"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@file_system.log "Returned (stderr): #{stderr.inspect}" unless stderr == ''
|
|
39
|
+
raise 'no response from curl on stdout' if stdout == ''
|
|
40
|
+
|
|
41
|
+
parse_response(command: command, result: stdout)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def capture3 command, stdin_data:
|
|
45
|
+
# In it's own method so we can mock it out in tests
|
|
46
|
+
Open3.capture3(command, stdin_data: stdin_data)
|
|
12
47
|
end
|
|
13
48
|
|
|
14
49
|
def call_url relative_url:
|
|
15
50
|
command = make_curl_command url: "#{@jira_url}#{relative_url}"
|
|
16
|
-
|
|
51
|
+
exec_and_parse_response command: command, stdin_data: nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_response command:, result:
|
|
17
55
|
begin
|
|
18
56
|
json = JSON.parse(result)
|
|
19
57
|
rescue # rubocop:disable Style/RescueStandardError
|
|
20
|
-
|
|
58
|
+
message = "Unable to parse results from #{sanitize_message(command)}"
|
|
59
|
+
@file_system.error message, more: result
|
|
60
|
+
raise message
|
|
21
61
|
end
|
|
22
62
|
|
|
23
63
|
raise "Download failed with: #{JSON.pretty_generate(json)}" unless json_successful?(json)
|
|
@@ -25,18 +65,11 @@ class JiraGateway
|
|
|
25
65
|
json
|
|
26
66
|
end
|
|
27
67
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@file_system.log log_entry
|
|
68
|
+
def sanitize_message message
|
|
69
|
+
token = @jira_api_token || @jira_personal_access_token
|
|
70
|
+
return message unless token # cookie based authentication
|
|
32
71
|
|
|
33
|
-
|
|
34
|
-
@file_system.log result unless $CHILD_STATUS.success?
|
|
35
|
-
return result if $CHILD_STATUS.success?
|
|
36
|
-
|
|
37
|
-
@file_system.log "Failed call with exit status #{$CHILD_STATUS.exitstatus}."
|
|
38
|
-
raise "Failed call with exit status #{$CHILD_STATUS.exitstatus}. " \
|
|
39
|
-
"See #{@file_system.logfile_name} for details"
|
|
72
|
+
message.gsub(token, '[API_TOKEN]')
|
|
40
73
|
end
|
|
41
74
|
|
|
42
75
|
def load_jira_config jira_config
|
|
@@ -56,7 +89,7 @@ class JiraGateway
|
|
|
56
89
|
@cookies = (jira_config['cookies'] || []).collect { |key, value| "#{key}=#{value}" }.join(';')
|
|
57
90
|
end
|
|
58
91
|
|
|
59
|
-
def make_curl_command url:
|
|
92
|
+
def make_curl_command url:, method: 'GET'
|
|
60
93
|
command = +''
|
|
61
94
|
command << 'curl'
|
|
62
95
|
command << ' -L' # follow redirects
|
|
@@ -65,8 +98,13 @@ class JiraGateway
|
|
|
65
98
|
command << " --cookie #{@cookies.inspect}" unless @cookies.empty?
|
|
66
99
|
command << " --user #{@jira_email}:#{@jira_api_token}" if @jira_api_token
|
|
67
100
|
command << " -H \"Authorization: Bearer #{@jira_personal_access_token}\"" if @jira_personal_access_token
|
|
68
|
-
command <<
|
|
101
|
+
command << " --request #{method}"
|
|
102
|
+
if method == 'POST'
|
|
103
|
+
command << ' --data @-'
|
|
104
|
+
command << ' --header "Content-Type: application/json"'
|
|
105
|
+
end
|
|
69
106
|
command << ' --header "Accept: application/json"'
|
|
107
|
+
command << ' --show-error --fail' # Better diagnostics when the server returns an error
|
|
70
108
|
command << " --url \"#{url}\""
|
|
71
109
|
command
|
|
72
110
|
end
|
|
@@ -58,7 +58,16 @@ class ProjectConfig
|
|
|
58
58
|
|
|
59
59
|
def load_settings
|
|
60
60
|
# This is the weird exception that we don't ever want mocked out so we skip FileSystem entirely.
|
|
61
|
-
JSON.parse(File.read(File.join(__dir__, 'settings.json'), encoding: 'UTF-8'))
|
|
61
|
+
settings = JSON.parse(File.read(File.join(__dir__, 'settings.json'), encoding: 'UTF-8'))
|
|
62
|
+
|
|
63
|
+
if settings['blocked_color']
|
|
64
|
+
file_system.deprecated message: 'blocked color should be set via css now', date: '2024-05-03'
|
|
65
|
+
end
|
|
66
|
+
if settings['stalled_color']
|
|
67
|
+
file_system.deprecated message: 'stalled color should be set via css now', date: '2024-05-03'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
settings
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def guess_project_id
|
|
@@ -114,10 +123,14 @@ class ProjectConfig
|
|
|
114
123
|
def file_prefix prefix
|
|
115
124
|
# The file_prefix has to be set before almost everything else. It really should have been an attribute
|
|
116
125
|
# on the project declaration itself. Hindsight is 20/20.
|
|
126
|
+
|
|
127
|
+
# There can only be one of these
|
|
117
128
|
if @file_prefix
|
|
118
|
-
raise "file_prefix
|
|
129
|
+
raise "file_prefix can only be set once. Was #{@file_prefix.inspect} and now changed to #{prefix.inspect}."
|
|
119
130
|
end
|
|
120
131
|
|
|
132
|
+
raise_if_prefix_already_used(prefix)
|
|
133
|
+
|
|
121
134
|
@file_prefix = prefix
|
|
122
135
|
|
|
123
136
|
# Yes, this is a wierd place to be initializing this. Unfortunately, it has to happen after the file_prefix
|
|
@@ -130,8 +143,21 @@ class ProjectConfig
|
|
|
130
143
|
@file_prefix
|
|
131
144
|
end
|
|
132
145
|
|
|
133
|
-
def
|
|
134
|
-
|
|
146
|
+
def raise_if_prefix_already_used prefix
|
|
147
|
+
@exporter.project_configs.each do |project|
|
|
148
|
+
next unless project.get_file_prefix(raise_if_not_set: false) == prefix && project.target_path == target_path
|
|
149
|
+
|
|
150
|
+
raise "Project #{name.inspect} specifies file prefix #{prefix.inspect}, " \
|
|
151
|
+
"but that is already used by project #{project.name.inspect} in the same target path #{target_path.inspect}. " \
|
|
152
|
+
'This is almost guaranteed to be too much copy and paste in your configuration. ' \
|
|
153
|
+
'File prefixes must be unique within a directory.'
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def get_file_prefix raise_if_not_set: true
|
|
158
|
+
if @file_prefix.nil? && raise_if_not_set
|
|
159
|
+
raise 'file_prefix has not been set yet. Move it to the top of the project declaration.'
|
|
160
|
+
end
|
|
135
161
|
|
|
136
162
|
@file_prefix
|
|
137
163
|
end
|
|
@@ -278,8 +304,9 @@ class ProjectConfig
|
|
|
278
304
|
file_system.foreach(@target_path) do |file|
|
|
279
305
|
next unless file =~ /^#{get_file_prefix}_board_(\d+)_sprints_\d+.json$/
|
|
280
306
|
|
|
307
|
+
board_id = $1.to_i
|
|
281
308
|
file_path = File.join(@target_path, file)
|
|
282
|
-
board = @all_boards[
|
|
309
|
+
board = @all_boards[board_id]
|
|
283
310
|
unless board
|
|
284
311
|
@exporter.file_system.log(
|
|
285
312
|
'Found sprint data but can\'t find a matching board in config. ' \
|
|
@@ -335,6 +362,12 @@ class ProjectConfig
|
|
|
335
362
|
json.each { |user_data| @users << User.new(raw: user_data) }
|
|
336
363
|
end
|
|
337
364
|
|
|
365
|
+
def atlassian_document_format
|
|
366
|
+
@atlassian_document_format ||= AtlassianDocumentFormat.new(
|
|
367
|
+
users: @users, timezone_offset: exporter.timezone_offset
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
|
|
338
371
|
def to_time string, end_of_day: false
|
|
339
372
|
time = end_of_day ? '23:59:59' : '00:00:00'
|
|
340
373
|
string = "#{string}T#{time}#{exporter.timezone_offset}" if string.match?(/^\d{4}-\d{2}-\d{2}$/)
|
|
@@ -526,6 +559,7 @@ class ProjectConfig
|
|
|
526
559
|
end
|
|
527
560
|
|
|
528
561
|
def discard_changes_before status_becomes: nil, &block
|
|
562
|
+
cycletimes_touched = Set.new
|
|
529
563
|
if status_becomes
|
|
530
564
|
status_becomes = [status_becomes] unless status_becomes.is_a? Array
|
|
531
565
|
|
|
@@ -558,6 +592,7 @@ class ProjectConfig
|
|
|
558
592
|
next if original_start_time.nil?
|
|
559
593
|
|
|
560
594
|
issue.discard_changes_before cutoff_time
|
|
595
|
+
cycletimes_touched << issue.board.cycletime
|
|
561
596
|
|
|
562
597
|
next unless cutoff_time
|
|
563
598
|
next if original_start_time > cutoff_time # ie the cutoff would have made no difference.
|
|
@@ -568,5 +603,7 @@ class ProjectConfig
|
|
|
568
603
|
issue: issue
|
|
569
604
|
}
|
|
570
605
|
end
|
|
606
|
+
|
|
607
|
+
cycletimes_touched.each { |c| c.flush_cache }
|
|
571
608
|
end
|
|
572
609
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# When strings are serialized into JSON, they're converted to actual strings. The purpose
|
|
4
|
+
# of this class is to allow raw javascript to be passed through.
|
|
5
|
+
class RawJavascript
|
|
6
|
+
def initialize content
|
|
7
|
+
@content = content
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_json(*_args)
|
|
11
|
+
@content
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -7,5 +7,7 @@
|
|
|
7
7
|
"flagged_means_blocked": true,
|
|
8
8
|
|
|
9
9
|
"expedited_priority_names": ["Critical", "Highest"],
|
|
10
|
-
"priority_order": ["Lowest", "Low", "Medium", "High", "Highest"]
|
|
10
|
+
"priority_order": ["Lowest", "Low", "Medium", "High", "Highest"],
|
|
11
|
+
|
|
12
|
+
"cache_cycletime_calculations": true
|
|
11
13
|
}
|
data/lib/jirametrics/sprint.rb
CHANGED
|
@@ -13,6 +13,7 @@ class Sprint
|
|
|
13
13
|
def id = @raw['id']
|
|
14
14
|
def active? = (@raw['state'] == 'active')
|
|
15
15
|
def closed? = (@raw['state'] == 'closed')
|
|
16
|
+
def future? = (@raw['state'] == 'future')
|
|
16
17
|
|
|
17
18
|
def completed_at? time
|
|
18
19
|
completed_at = completed_time
|
|
@@ -36,6 +37,17 @@ class Sprint
|
|
|
36
37
|
def goal = @raw['goal']
|
|
37
38
|
def name = @raw['name']
|
|
38
39
|
|
|
40
|
+
def day_count
|
|
41
|
+
return '' if future?
|
|
42
|
+
|
|
43
|
+
if closed?
|
|
44
|
+
days = (completed_time.to_date - start_time.to_date).to_i + 1
|
|
45
|
+
else
|
|
46
|
+
days = (end_time.to_date - start_time.to_date).to_i + 1
|
|
47
|
+
end
|
|
48
|
+
"#{days} days"
|
|
49
|
+
end
|
|
50
|
+
|
|
39
51
|
private
|
|
40
52
|
|
|
41
53
|
def parse_time time_string
|
|
@@ -48,8 +48,9 @@ class SprintBurndown < ChartBase
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def run
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return nil unless current_board.scrum?
|
|
52
|
+
|
|
53
|
+
sprints = sprints_in_time_range current_board
|
|
53
54
|
|
|
54
55
|
change_data_by_sprint = {}
|
|
55
56
|
sprints.each do |sprint|
|
|
@@ -110,6 +111,9 @@ class SprintBurndown < ChartBase
|
|
|
110
111
|
|
|
111
112
|
def sprints_in_time_range board
|
|
112
113
|
board.sprints.select do |sprint|
|
|
114
|
+
# If it's never been started then it's just a holding area. Ignore it.
|
|
115
|
+
next if sprint.future?
|
|
116
|
+
|
|
113
117
|
sprint_end_time = sprint.completed_time || sprint.end_time
|
|
114
118
|
sprint_start_time = sprint.start_time
|
|
115
119
|
next false if sprint_start_time.nil?
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Stitcher < HtmlGenerator
|
|
4
|
+
class StitchContent
|
|
5
|
+
include ValueEquality
|
|
6
|
+
|
|
7
|
+
attr_reader :file, :title, :content, :type
|
|
8
|
+
|
|
9
|
+
def initialize file:, title:, type:, content:
|
|
10
|
+
@file = file
|
|
11
|
+
@title = title
|
|
12
|
+
@content = content
|
|
13
|
+
@type = type
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :loaded_files, :all_stitches
|
|
18
|
+
|
|
19
|
+
def initialize file_system:
|
|
20
|
+
super()
|
|
21
|
+
self.file_system = file_system
|
|
22
|
+
@all_stitches = []
|
|
23
|
+
@loaded_files = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run stitch_file:
|
|
27
|
+
output_filename = make_output_filename stitch_file
|
|
28
|
+
file_system.log "Creating file #{output_filename.inspect}", also_write_to_stderr: true
|
|
29
|
+
erb = ERB.new file_system.load(stitch_file)
|
|
30
|
+
@sections = [[erb.result(binding), :body]]
|
|
31
|
+
create_html output_filename: output_filename, settings: {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def make_output_filename input_filename
|
|
35
|
+
if /^(.+)\.erb$/ =~ input_filename
|
|
36
|
+
"#{$1}.html"
|
|
37
|
+
else
|
|
38
|
+
"#{input_filename}.html"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def grab_by_title title, from_file:, type: 'chart'
|
|
43
|
+
parse_file from_file
|
|
44
|
+
stitch_content = @all_stitches.find { |s| s.file == from_file && s.title == title && s.type == type }
|
|
45
|
+
return stitch_content.content if stitch_content
|
|
46
|
+
|
|
47
|
+
raise "Unable to find content in file #{from_file.inspect} matching title: #{title.inspect}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_file filename
|
|
51
|
+
return false if @loaded_files.include? filename
|
|
52
|
+
|
|
53
|
+
# To match: <!-- seam-start | chart78 | GithubPrScatterplot | PR Scatterplot | chart -->
|
|
54
|
+
regex = /^<!-- seam-(?<seam>start|end) \| (?<id>[^|]+) \| (?<clazz>[^|]+) \| (?<title>[^|]+) \| (?<type>[^|]+) -->$/
|
|
55
|
+
content = nil
|
|
56
|
+
file_system.load(filename).lines do |line|
|
|
57
|
+
matches = line.match(regex)
|
|
58
|
+
if matches
|
|
59
|
+
if matches[:seam] == 'start'
|
|
60
|
+
content = +''
|
|
61
|
+
else
|
|
62
|
+
@all_stitches << Stitcher::StitchContent.new(
|
|
63
|
+
file: filename, title: matches[:title], type: matches[:type], content: content
|
|
64
|
+
)
|
|
65
|
+
content = nil
|
|
66
|
+
end
|
|
67
|
+
elsif content
|
|
68
|
+
content << line
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@loaded_files << filename
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/jirametrics.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
|
+
require 'require_all'
|
|
5
|
+
|
|
6
|
+
# This one does need to be loaded early. The rest will be loaded later.
|
|
7
|
+
require 'jirametrics/file_system'
|
|
4
8
|
|
|
5
9
|
class JiraMetrics < Thor
|
|
6
10
|
def self.exit_on_failure?
|
|
@@ -43,81 +47,33 @@ class JiraMetrics < Thor
|
|
|
43
47
|
|
|
44
48
|
option :config
|
|
45
49
|
desc 'info', 'Dump information about one issue'
|
|
46
|
-
def info
|
|
50
|
+
def info key
|
|
47
51
|
load_config options[:config]
|
|
48
|
-
Exporter.instance.info(
|
|
52
|
+
Exporter.instance.info(key, name_filter: options[:name] || '*')
|
|
49
53
|
end
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
+
option :config
|
|
56
|
+
desc 'stitch', 'Dump information about one issue'
|
|
57
|
+
def stitch stitch_file = 'stitcher.erb'
|
|
58
|
+
load_config options[:config]
|
|
59
|
+
Exporter.instance.stitch stitch_file
|
|
60
|
+
end
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
config_file = File.absolute_path(config_file).to_s
|
|
60
|
-
else
|
|
61
|
-
puts "Cannot find configuration file #{config_file.inspect}"
|
|
62
|
-
exit 1
|
|
63
|
-
end
|
|
62
|
+
no_commands do
|
|
63
|
+
def load_config config_file, file_system: FileSystem.new
|
|
64
|
+
config_file = './config.rb' if config_file.nil?
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
if file_system.file_exist? config_file
|
|
67
|
+
# The fact that File.exist can see the file does not mean that require will be
|
|
68
|
+
# able to load it. Convert this to an absolute pathname now for require.
|
|
69
|
+
config_file = File.absolute_path(config_file).to_s
|
|
70
|
+
else
|
|
71
|
+
file_system.error "Cannot find configuration file #{config_file.inspect}"
|
|
72
|
+
exit 1
|
|
73
|
+
end
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
require 'jirametrics/file_config'
|
|
78
|
-
require 'jirametrics/jira_gateway'
|
|
79
|
-
require 'jirametrics/trend_line_calculator'
|
|
80
|
-
require 'jirametrics/status'
|
|
81
|
-
require 'jirametrics/issue_link'
|
|
82
|
-
require 'jirametrics/estimate_accuracy_chart'
|
|
83
|
-
require 'jirametrics/status_collection'
|
|
84
|
-
require 'jirametrics/sprint'
|
|
85
|
-
require 'jirametrics/issue'
|
|
86
|
-
require 'jirametrics/daily_wip_by_age_chart'
|
|
87
|
-
require 'jirametrics/daily_wip_by_parent_chart'
|
|
88
|
-
require 'jirametrics/aging_work_in_progress_chart'
|
|
89
|
-
require 'jirametrics/cycletime_scatterplot'
|
|
90
|
-
require 'jirametrics/flow_efficiency_scatterplot'
|
|
91
|
-
require 'jirametrics/sprint_issue_change_data'
|
|
92
|
-
require 'jirametrics/cycletime_histogram'
|
|
93
|
-
require 'jirametrics/daily_wip_by_blocked_stalled_chart'
|
|
94
|
-
require 'jirametrics/html_report_config'
|
|
95
|
-
require 'jirametrics/data_quality_report'
|
|
96
|
-
require 'jirametrics/aging_work_bar_chart'
|
|
97
|
-
require 'jirametrics/change_item'
|
|
98
|
-
require 'jirametrics/project_config'
|
|
99
|
-
require 'jirametrics/dependency_chart'
|
|
100
|
-
require 'jirametrics/cycletime_config'
|
|
101
|
-
require 'jirametrics/tree_organizer'
|
|
102
|
-
require 'jirametrics/aging_work_table'
|
|
103
|
-
require 'jirametrics/sprint_burndown'
|
|
104
|
-
require 'jirametrics/self_or_issue_dispatcher'
|
|
105
|
-
require 'jirametrics/throughput_chart'
|
|
106
|
-
require 'jirametrics/exporter'
|
|
107
|
-
require 'jirametrics/file_system'
|
|
108
|
-
require 'jirametrics/blocked_stalled_change'
|
|
109
|
-
require 'jirametrics/board_column'
|
|
110
|
-
require 'jirametrics/anonymizer'
|
|
111
|
-
require 'jirametrics/downloader'
|
|
112
|
-
require 'jirametrics/fix_version'
|
|
113
|
-
require 'jirametrics/download_config'
|
|
114
|
-
require 'jirametrics/columns_config'
|
|
115
|
-
require 'jirametrics/hierarchy_table'
|
|
116
|
-
require 'jirametrics/estimation_configuration'
|
|
117
|
-
require 'jirametrics/board'
|
|
118
|
-
require 'jirametrics/daily_view'
|
|
119
|
-
require 'jirametrics/user'
|
|
120
|
-
require 'jirametrics/atlassian_document_format'
|
|
121
|
-
load config_file
|
|
75
|
+
require_rel 'jirametrics'
|
|
76
|
+
load config_file
|
|
77
|
+
end
|
|
122
78
|
end
|
|
123
79
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jirametrics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '2.
|
|
4
|
+
version: '2.22'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Bowler
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: random-word
|
|
@@ -66,6 +66,7 @@ files:
|
|
|
66
66
|
- lib/jirametrics/aging_work_table.rb
|
|
67
67
|
- lib/jirametrics/anonymizer.rb
|
|
68
68
|
- lib/jirametrics/atlassian_document_format.rb
|
|
69
|
+
- lib/jirametrics/bar_chart_range.rb
|
|
69
70
|
- lib/jirametrics/blocked_stalled_change.rb
|
|
70
71
|
- lib/jirametrics/board.rb
|
|
71
72
|
- lib/jirametrics/board_column.rb
|
|
@@ -87,6 +88,8 @@ files:
|
|
|
87
88
|
- lib/jirametrics/dependency_chart.rb
|
|
88
89
|
- lib/jirametrics/download_config.rb
|
|
89
90
|
- lib/jirametrics/downloader.rb
|
|
91
|
+
- lib/jirametrics/downloader_for_cloud.rb
|
|
92
|
+
- lib/jirametrics/downloader_for_data_center.rb
|
|
90
93
|
- lib/jirametrics/estimate_accuracy_chart.rb
|
|
91
94
|
- lib/jirametrics/estimation_configuration.rb
|
|
92
95
|
- lib/jirametrics/examples/aggregated_project.rb
|
|
@@ -113,14 +116,17 @@ files:
|
|
|
113
116
|
- lib/jirametrics/html/hierarchy_table.erb
|
|
114
117
|
- lib/jirametrics/html/index.css
|
|
115
118
|
- lib/jirametrics/html/index.erb
|
|
119
|
+
- lib/jirametrics/html/index.js
|
|
116
120
|
- lib/jirametrics/html/sprint_burndown.erb
|
|
117
121
|
- lib/jirametrics/html/throughput_chart.erb
|
|
122
|
+
- lib/jirametrics/html_generator.rb
|
|
118
123
|
- lib/jirametrics/html_report_config.rb
|
|
119
124
|
- lib/jirametrics/issue.rb
|
|
120
125
|
- lib/jirametrics/issue_collection.rb
|
|
121
126
|
- lib/jirametrics/issue_link.rb
|
|
122
127
|
- lib/jirametrics/jira_gateway.rb
|
|
123
128
|
- lib/jirametrics/project_config.rb
|
|
129
|
+
- lib/jirametrics/raw_javascript.rb
|
|
124
130
|
- lib/jirametrics/rules.rb
|
|
125
131
|
- lib/jirametrics/self_or_issue_dispatcher.rb
|
|
126
132
|
- lib/jirametrics/settings.json
|
|
@@ -129,6 +135,7 @@ files:
|
|
|
129
135
|
- lib/jirametrics/sprint_issue_change_data.rb
|
|
130
136
|
- lib/jirametrics/status.rb
|
|
131
137
|
- lib/jirametrics/status_collection.rb
|
|
138
|
+
- lib/jirametrics/stitcher.rb
|
|
132
139
|
- lib/jirametrics/throughput_chart.rb
|
|
133
140
|
- lib/jirametrics/tree_organizer.rb
|
|
134
141
|
- lib/jirametrics/trend_line_calculator.rb
|
|
@@ -156,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
163
|
- !ruby/object:Gem::Version
|
|
157
164
|
version: '0'
|
|
158
165
|
requirements: []
|
|
159
|
-
rubygems_version: 3.6.
|
|
166
|
+
rubygems_version: 3.6.9
|
|
160
167
|
specification_version: 4
|
|
161
168
|
summary: Extract Jira metrics
|
|
162
169
|
test_files: []
|