jirametrics 2.14 → 2.15
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/atlassian_document_format.rb +1 -1
- data/lib/jirametrics/chart_base.rb +3 -2
- data/lib/jirametrics/cycletime_histogram.rb +3 -1
- data/lib/jirametrics/daily_view.rb +5 -19
- data/lib/jirametrics/dependency_chart.rb +4 -1
- data/lib/jirametrics/expedited_chart.rb +1 -1
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +1 -1
- data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
- data/lib/jirametrics/html/cycletime_histogram.erb +2 -2
- data/lib/jirametrics/html/index.css +0 -10
- data/lib/jirametrics/html/index.erb +1 -33
- data/lib/jirametrics/html/index.js +90 -0
- data/lib/jirametrics/html/sprint_burndown.erb +5 -3
- data/lib/jirametrics/html_report_config.rb +2 -2
- data/lib/jirametrics/issue.rb +14 -9
- data/lib/jirametrics/jira_gateway.rb +11 -2
- data/lib/jirametrics/project_config.rb +6 -0
- data/lib/jirametrics.rb +17 -68
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8045db95ffbb1368c3df8be3ee88c88ddb1a3d2fee46c989ba9d94e8fdb4ac3
|
4
|
+
data.tar.gz: a4a245da182c96238ec99918eefaf097c3fcc3cd3f4a09f85d4fbdd25020b713
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb301db36a63ec0e2da49c5f7ce1f7c58ee44f9c1ab1dad7811667a72d598a8a36282bccc795bebf755706ea1b0d1f0ebdf092f75fe89dc128203645182b9a30
|
7
|
+
data.tar.gz: e1d08efa142f9d824621831cfc465fd6d17ba3c374f0259d860798feec2706214989c9eac9e41c5eafbd66be0d4c147c29bdec638eec7e956b87858384bf6bb8
|
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
class ChartBase
|
4
4
|
attr_accessor :timezone_offset, :board_id, :all_boards, :date_range,
|
5
|
-
:time_range, :data_quality, :holiday_dates, :settings, :issues, :file_system,
|
5
|
+
:time_range, :data_quality, :holiday_dates, :settings, :issues, :file_system,
|
6
|
+
:atlassian_document_format
|
6
7
|
attr_writer :aggregated_project
|
7
8
|
attr_reader :canvas_width, :canvas_height
|
8
9
|
|
@@ -44,7 +45,7 @@ class ChartBase
|
|
44
45
|
|
45
46
|
def render_top_text caller_binding
|
46
47
|
result = +''
|
47
|
-
result << "<h1>#{@header_text}</h1>" if @header_text
|
48
|
+
result << "<h1 class='foldable'>#{@header_text}</h1>" if @header_text
|
48
49
|
result << ERB.new(@description_text).result(caller_binding) if @description_text
|
49
50
|
result
|
50
51
|
end
|
@@ -62,7 +62,9 @@ class CycletimeHistogram < ChartBase
|
|
62
62
|
)
|
63
63
|
end
|
64
64
|
|
65
|
-
|
65
|
+
if data_sets.empty?
|
66
|
+
return "<h1 class='foldable'>#{@header_text}</h1><div>No data matched the selected criteria. Nothing to show.</div>"
|
67
|
+
end
|
66
68
|
|
67
69
|
wrap_and_render(binding, __FILE__)
|
68
70
|
end
|
@@ -23,7 +23,7 @@ class DailyView < ChartBase
|
|
23
23
|
def run
|
24
24
|
aging_issues = select_aging_issues
|
25
25
|
|
26
|
-
return "<h1>#{@header_text}</h1>There are no items currently in progress" if aging_issues.empty?
|
26
|
+
return "<h1 class='foldable'>#{@header_text}</h1>There are no items currently in progress" if aging_issues.empty?
|
27
27
|
|
28
28
|
result = +''
|
29
29
|
result << render_top_text(binding)
|
@@ -33,10 +33,6 @@ class DailyView < ChartBase
|
|
33
33
|
result
|
34
34
|
end
|
35
35
|
|
36
|
-
def atlassian_document_format
|
37
|
-
@atlassian_document_format ||= AtlassianDocumentFormat.new(users: users, timezone_offset: timezone_offset)
|
38
|
-
end
|
39
|
-
|
40
36
|
def select_aging_issues
|
41
37
|
aging_issues = issues.select do |issue|
|
42
38
|
started_at, stopped_at = issue.board.cycletime.started_stopped_times(issue)
|
@@ -170,13 +166,7 @@ class DailyView < ChartBase
|
|
170
166
|
|
171
167
|
return lines if subtasks.empty?
|
172
168
|
|
173
|
-
|
174
|
-
lines <<
|
175
|
-
"<a href=\"javascript:toggle_visibility('open#{id}', 'close#{id}', 'section#{id}');\">" \
|
176
|
-
"<span id='open#{id}' style='display: none'>▶ Child issues</span>" \
|
177
|
-
"<span id='close#{id}'>▼ Child issues</span></a>"
|
178
|
-
lines << "<section id='section#{id}'>"
|
179
|
-
|
169
|
+
lines << '<section><div class="foldable">Child issues</div>'
|
180
170
|
lines += subtasks
|
181
171
|
lines << '</section>'
|
182
172
|
|
@@ -187,14 +177,9 @@ class DailyView < ChartBase
|
|
187
177
|
history = issue.changes.reverse
|
188
178
|
lines = []
|
189
179
|
|
190
|
-
|
191
|
-
lines << [
|
192
|
-
"<a href=\"javascript:toggle_visibility('open#{id}', 'close#{id}', 'table#{id}');\">" \
|
193
|
-
"<span id='open#{id}'>▶ Issue History</span>" \
|
194
|
-
"<span id='close#{id}' style='display: none'>▼ Issue History</span></a>"
|
195
|
-
]
|
180
|
+
lines << '<section><div class="foldable startFolded">Issue history</div>'
|
196
181
|
table = +''
|
197
|
-
table <<
|
182
|
+
table << '<table>'
|
198
183
|
history.each do |c|
|
199
184
|
time = c.time.strftime '%b %d, %I:%M%P'
|
200
185
|
|
@@ -207,6 +192,7 @@ class DailyView < ChartBase
|
|
207
192
|
end
|
208
193
|
table << '</table>'
|
209
194
|
lines << [table]
|
195
|
+
lines << '</section>'
|
210
196
|
lines
|
211
197
|
end
|
212
198
|
|
@@ -51,7 +51,10 @@ class DependencyChart < ChartBase
|
|
51
51
|
instance_eval(&@rules_block) if @rules_block
|
52
52
|
|
53
53
|
dot_graph = build_dot_graph
|
54
|
-
|
54
|
+
if dot_graph.nil?
|
55
|
+
return "<h1 class='foldable'>#{@header_text}</h1>" \
|
56
|
+
'<div>No data matched the selected criteria. Nothing to show.</div>'
|
57
|
+
end
|
55
58
|
|
56
59
|
svg = execute_graphviz(dot_graph.join("\n"))
|
57
60
|
"<h1>#{@header_text}</h1><div>#{@description_text}</div>#{shrink_svg svg}"
|
@@ -48,7 +48,7 @@ class ExpeditedChart < ChartBase
|
|
48
48
|
end
|
49
49
|
|
50
50
|
if data_sets.empty?
|
51
|
-
'<h1>Expedited work</h1>There is no expedited work in this time period
|
51
|
+
'<h1 class="foldable">Expedited work</h1><p>There is no expedited work in this time period.</p>'
|
52
52
|
else
|
53
53
|
wrap_and_render(binding, __FILE__)
|
54
54
|
end
|
@@ -60,7 +60,7 @@ class FlowEfficiencyScatterplot < ChartBase
|
|
60
60
|
create_dataset(issues: issues, label: rules.label, color: rules.color)
|
61
61
|
end
|
62
62
|
|
63
|
-
return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if data_sets.empty?
|
63
|
+
return "<h1 class='foldable'>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if data_sets.empty?
|
64
64
|
|
65
65
|
wrap_and_render(binding, __FILE__)
|
66
66
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
<table class='standard' id='<%= issues_id %>'
|
1
|
+
<div class='foldable startFolded'>Show details</div>
|
2
|
+
<table class='standard' id='<%= issues_id %>'>
|
3
3
|
<thead>
|
4
4
|
<tr>
|
5
5
|
<th>Issue</th>
|
@@ -6,8 +6,8 @@ if show_stats
|
|
6
6
|
link_id = next_id
|
7
7
|
issues_id = next_id
|
8
8
|
%>
|
9
|
-
|
10
|
-
<div id="<%= issues_id %>" style="
|
9
|
+
<div class='foldable' style="padding-left: 1em;">Statistics</div>
|
10
|
+
<div id="<%= issues_id %>" style="padding-left: 1em;">
|
11
11
|
<div>
|
12
12
|
<table class="standard">
|
13
13
|
<tr>
|
@@ -78,11 +78,6 @@ body {
|
|
78
78
|
color: var(--default-text-color);
|
79
79
|
}
|
80
80
|
|
81
|
-
h1 {
|
82
|
-
border: 1px solid black;
|
83
|
-
background: lightgray;
|
84
|
-
padding-left: 0.2em;
|
85
|
-
}
|
86
81
|
dl, dd, dt {
|
87
82
|
padding: 0;
|
88
83
|
margin: 0;
|
@@ -244,11 +239,6 @@ div.child_issue {
|
|
244
239
|
--daily-view-selected-issue-background: #474747;
|
245
240
|
}
|
246
241
|
|
247
|
-
h1 {
|
248
|
-
color: #e0e0e0;
|
249
|
-
background-color: #656565;
|
250
|
-
}
|
251
|
-
|
252
242
|
a[href] {
|
253
243
|
color: #1e8ad6;
|
254
244
|
}
|
@@ -7,39 +7,7 @@
|
|
7
7
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>
|
8
8
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/1.2.2/chartjs-plugin-annotation.min.js" integrity="sha512-HycvvBSFvDEVyJ0tjE2rPmymkt6XqsP/Zo96XgLRjXwn6SecQqsn+6V/7KYev66OshZZ9+f9AttCGmYqmzytiw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
9
9
|
<script type="text/javascript">
|
10
|
-
|
11
|
-
link_text = document.getElementById(link_id).textContent
|
12
|
-
if( link_text == 'Show details') {
|
13
|
-
document.getElementById(link_id).textContent = 'Hide details'
|
14
|
-
document.getElementById(issues_id).style.display = 'block'
|
15
|
-
}
|
16
|
-
else {
|
17
|
-
document.getElementById(link_id).textContent = 'Show details'
|
18
|
-
document.getElementById(issues_id).style.display = 'none'
|
19
|
-
}
|
20
|
-
}
|
21
|
-
|
22
|
-
function toggle_visibility(open_link_id, close_link_id, toggleable_id) {
|
23
|
-
let open_link = document.getElementById(open_link_id)
|
24
|
-
let close_link = document.getElementById(close_link_id)
|
25
|
-
let toggleable_element = document.getElementById(toggleable_id)
|
26
|
-
|
27
|
-
if(open_link.style.display == 'none') {
|
28
|
-
open_link.style.display = 'block'
|
29
|
-
close_link.style.display = 'none'
|
30
|
-
toggleable_element.style.display = 'none'
|
31
|
-
}
|
32
|
-
else {
|
33
|
-
open_link.style.display = 'none'
|
34
|
-
close_link.style.display = 'block'
|
35
|
-
toggleable_element.style.display = 'block'
|
36
|
-
}
|
37
|
-
}
|
38
|
-
// If we switch between light/dark mode then force a refresh so all charts will redraw correctly
|
39
|
-
// in the other colour scheme.
|
40
|
-
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
41
|
-
location.reload()
|
42
|
-
})
|
10
|
+
<%= javascript %>
|
43
11
|
</script>
|
44
12
|
<style>
|
45
13
|
<%= css %>
|
@@ -0,0 +1,90 @@
|
|
1
|
+
function makeFoldable() {
|
2
|
+
// Get all elements with the "foldable" class
|
3
|
+
const foldableElements = document.querySelectorAll('.foldable');
|
4
|
+
|
5
|
+
if (foldableElements.length === 0) {
|
6
|
+
return; // No foldable elements found
|
7
|
+
}
|
8
|
+
|
9
|
+
// Process each foldable element
|
10
|
+
foldableElements.forEach((element, index) => {
|
11
|
+
// Skip if this is the footer element
|
12
|
+
if (element.id === 'footer') {
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
|
16
|
+
// Create a unique ID for this section
|
17
|
+
const sectionId = `foldable-section-${index}`;
|
18
|
+
const toggleId = `foldable-toggle-${index}`;
|
19
|
+
|
20
|
+
// Create a container div for the foldable element and its content
|
21
|
+
const container = document.createElement('div');
|
22
|
+
container.className = 'foldable-section';
|
23
|
+
container.id = sectionId;
|
24
|
+
|
25
|
+
// Create a toggle button
|
26
|
+
const toggleButton = document.createElement(element.tagName); //'button');
|
27
|
+
toggleButton.id = toggleId;
|
28
|
+
toggleButton.className = 'foldable-toggle-btn';
|
29
|
+
toggleButton.innerHTML = '▼ ' + element.textContent;
|
30
|
+
|
31
|
+
// Create a content container
|
32
|
+
const contentContainer = document.createElement('div');
|
33
|
+
contentContainer.className = 'foldable-content';
|
34
|
+
contentContainer.style.cssText = `
|
35
|
+
border-left: 2px solid #ccc;
|
36
|
+
padding-left: 15px;
|
37
|
+
`;
|
38
|
+
|
39
|
+
// Move the foldable element into the container and replace it with the toggle button
|
40
|
+
element.parentNode.insertBefore(container, element);
|
41
|
+
container.appendChild(toggleButton);
|
42
|
+
container.appendChild(contentContainer);
|
43
|
+
|
44
|
+
// Move all elements between this foldable element and the next foldable element (or end of document) into the content container
|
45
|
+
let nextElement = element.nextElementSibling;
|
46
|
+
while (nextElement && !nextElement.classList.contains('foldable')) {
|
47
|
+
// Skip the footer element
|
48
|
+
if (nextElement.id === 'footer') {
|
49
|
+
break;
|
50
|
+
}
|
51
|
+
|
52
|
+
const temp = nextElement.nextElementSibling;
|
53
|
+
contentContainer.appendChild(nextElement);
|
54
|
+
nextElement = temp;
|
55
|
+
}
|
56
|
+
|
57
|
+
// Remove the original foldable element
|
58
|
+
element.remove();
|
59
|
+
|
60
|
+
// Add click event to toggle visibility
|
61
|
+
toggleButton.addEventListener('click', function() {
|
62
|
+
const content = this.nextElementSibling;
|
63
|
+
if (content.style.display === 'none') {
|
64
|
+
content.style.display = 'block';
|
65
|
+
this.innerHTML = '▼ ' + this.innerHTML.substring(2);
|
66
|
+
} else {
|
67
|
+
content.style.display = 'none';
|
68
|
+
this.innerHTML = '▶ ' + this.innerHTML.substring(2);
|
69
|
+
}
|
70
|
+
});
|
71
|
+
|
72
|
+
// Initially show the content (you can change this to 'none' if you want sections collapsed by default)
|
73
|
+
contentContainer.style.display = 'block';
|
74
|
+
if(element.classList.contains('startFolded')) {
|
75
|
+
toggleButton.click();
|
76
|
+
}
|
77
|
+
});
|
78
|
+
}
|
79
|
+
|
80
|
+
// Auto-initialize when DOM is loaded
|
81
|
+
document.addEventListener('DOMContentLoaded', function() {
|
82
|
+
makeFoldable();
|
83
|
+
});
|
84
|
+
|
85
|
+
|
86
|
+
// If we switch between light/dark mode then force a refresh so all charts will redraw correctly
|
87
|
+
// in the other colour scheme.
|
88
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
89
|
+
location.reload()
|
90
|
+
})
|
@@ -68,8 +68,9 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
68
68
|
link_id = next_id
|
69
69
|
issues_id = next_id
|
70
70
|
%>
|
71
|
-
|
72
|
-
<div
|
71
|
+
<section>
|
72
|
+
<div class='foldable startFolded'>Show statistics</div>
|
73
|
+
<div id="<%= issues_id %>">
|
73
74
|
<table class='standard' style="margin-left: 1em;">
|
74
75
|
<thead>
|
75
76
|
<th>Sprint</th>
|
@@ -109,4 +110,5 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
109
110
|
<% end %>
|
110
111
|
</ul>
|
111
112
|
</p>
|
112
|
-
</div>
|
113
|
+
</div>
|
114
|
+
</section>
|
@@ -72,6 +72,7 @@ class HtmlReportConfig
|
|
72
72
|
|
73
73
|
html_directory = "#{Pathname.new(File.realpath(__FILE__)).dirname}/html"
|
74
74
|
css = load_css html_directory: html_directory
|
75
|
+
javascript = file_system.load(File.join(html_directory, 'index.js'))
|
75
76
|
erb = ERB.new file_system.load(File.join(html_directory, 'index.erb'))
|
76
77
|
file_system.save_file content: erb.result(binding), filename: @file_config.output_filename
|
77
78
|
end
|
@@ -87,7 +88,6 @@ class HtmlReportConfig
|
|
87
88
|
def load_css html_directory:
|
88
89
|
base_css_filename = File.join(html_directory, 'index.css')
|
89
90
|
base_css = file_system.load(base_css_filename)
|
90
|
-
log("Loaded CSS: #{base_css_filename}")
|
91
91
|
|
92
92
|
extra_css_filename = settings['include_css']
|
93
93
|
if extra_css_filename
|
@@ -160,7 +160,7 @@ class HtmlReportConfig
|
|
160
160
|
chart.time_range = project_config.time_range
|
161
161
|
chart.timezone_offset = timezone_offset
|
162
162
|
chart.settings = settings
|
163
|
-
chart.
|
163
|
+
chart.atlassian_document_format = project_config.atlassian_document_format
|
164
164
|
|
165
165
|
chart.all_boards = project_config.all_boards
|
166
166
|
chart.board_id = find_board_id
|
data/lib/jirametrics/issue.rb
CHANGED
@@ -608,7 +608,7 @@ class Issue
|
|
608
608
|
|
609
609
|
def dump
|
610
610
|
result = +''
|
611
|
-
result << "#{key} (#{type}): #{compact_text summary, 200}\n"
|
611
|
+
result << "#{key} (#{type}): #{compact_text summary, max: 200}\n"
|
612
612
|
|
613
613
|
assignee = raw['fields']['assignee']
|
614
614
|
result << " [assignee] #{assignee['name'].inspect} <#{assignee['emailAddress']}>\n" unless assignee.nil?
|
@@ -705,6 +705,19 @@ class Issue
|
|
705
705
|
board.sprints.select { |s| sprint_ids.include? s.id }
|
706
706
|
end
|
707
707
|
|
708
|
+
def compact_text text, max: 60
|
709
|
+
return '' if text.nil?
|
710
|
+
|
711
|
+
if text.is_a? Hash
|
712
|
+
# We can't effectively compact it but we can convert it into a string.
|
713
|
+
text = @board.project_config.atlassian_document_format.to_html(text)
|
714
|
+
else
|
715
|
+
text = text.gsub(/\s+/, ' ').strip
|
716
|
+
text = "#{text[0...max]}..." if text.length > max
|
717
|
+
end
|
718
|
+
text
|
719
|
+
end
|
720
|
+
|
708
721
|
private
|
709
722
|
|
710
723
|
def load_history_into_changes
|
@@ -729,14 +742,6 @@ class Issue
|
|
729
742
|
end
|
730
743
|
end
|
731
744
|
|
732
|
-
def compact_text text, max = 60
|
733
|
-
return nil if text.nil?
|
734
|
-
|
735
|
-
text = text.gsub(/\s+/, ' ').strip
|
736
|
-
text = "#{text[0..max]}..." if text.length > max
|
737
|
-
text
|
738
|
-
end
|
739
|
-
|
740
745
|
def sort_changes!
|
741
746
|
@changes.sort! do |a, b|
|
742
747
|
# It's common that a resolved will happen at the same time as a status change.
|
@@ -17,7 +17,9 @@ class JiraGateway
|
|
17
17
|
begin
|
18
18
|
json = JSON.parse(result)
|
19
19
|
rescue # rubocop:disable Style/RescueStandardError
|
20
|
-
|
20
|
+
message = "Unable to parse results from #{sanitize_message(command)}"
|
21
|
+
@file_system.error message, more: result
|
22
|
+
raise message
|
21
23
|
end
|
22
24
|
|
23
25
|
raise "Download failed with: #{JSON.pretty_generate(json)}" unless json_successful?(json)
|
@@ -25,9 +27,16 @@ class JiraGateway
|
|
25
27
|
json
|
26
28
|
end
|
27
29
|
|
30
|
+
def sanitize_message message
|
31
|
+
token = @jira_api_token || @jira_personal_access_token
|
32
|
+
raise 'Neither Jira API Token or personal access token has been set' unless token
|
33
|
+
|
34
|
+
message.gsub(@jira_api_token, '[API_TOKEN]')
|
35
|
+
end
|
36
|
+
|
28
37
|
def call_command command
|
29
38
|
log_entry = " #{command.gsub(/\s+/, ' ')}"
|
30
|
-
log_entry = log_entry
|
39
|
+
log_entry = sanitize_message log_entry if @jira_api_token
|
31
40
|
@file_system.log log_entry
|
32
41
|
|
33
42
|
result = `#{command}`
|
@@ -352,6 +352,12 @@ class ProjectConfig
|
|
352
352
|
json.each { |user_data| @users << User.new(raw: user_data) }
|
353
353
|
end
|
354
354
|
|
355
|
+
def atlassian_document_format
|
356
|
+
@atlassian_document_format ||= AtlassianDocumentFormat.new(
|
357
|
+
users: @users, timezone_offset: exporter.timezone_offset
|
358
|
+
)
|
359
|
+
end
|
360
|
+
|
355
361
|
def to_time string, end_of_day: false
|
356
362
|
time = end_of_day ? '23:59:59' : '00:00:00'
|
357
363
|
string = "#{string}T#{time}#{exporter.timezone_offset}" if string.match?(/^\d{4}-\d{2}-\d{2}$/)
|
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 './lib/jirametrics/file_system'
|
4
8
|
|
5
9
|
class JiraMetrics < Thor
|
6
10
|
def self.exit_on_failure?
|
@@ -48,76 +52,21 @@ class JiraMetrics < Thor
|
|
48
52
|
Exporter.instance.info(keys, name_filter: options[:name] || '*')
|
49
53
|
end
|
50
54
|
|
51
|
-
|
55
|
+
no_commands do
|
56
|
+
def load_config config_file, file_system: FileSystem.new
|
57
|
+
config_file = './config.rb' if config_file.nil?
|
52
58
|
|
53
|
-
|
54
|
-
|
59
|
+
if File.exist? config_file
|
60
|
+
# The fact that File.exist can see the file does not mean that require will be
|
61
|
+
# able to load it. Convert this to an absolute pathname now for require.
|
62
|
+
config_file = File.absolute_path(config_file).to_s
|
63
|
+
else
|
64
|
+
file_system.error "Cannot find configuration file #{config_file.inspect}"
|
65
|
+
exit 1
|
66
|
+
end
|
55
67
|
|
56
|
-
|
57
|
-
|
58
|
-
# able to load it. Convert this to an absolute pathname now for require.
|
59
|
-
config_file = File.absolute_path(config_file).to_s
|
60
|
-
else
|
61
|
-
puts "Cannot find configuration file #{config_file.inspect}"
|
62
|
-
exit 1
|
68
|
+
require_rel 'jirametrics'
|
69
|
+
load config_file
|
63
70
|
end
|
64
|
-
|
65
|
-
require 'jirametrics/value_equality'
|
66
|
-
require 'jirametrics/chart_base'
|
67
|
-
require 'jirametrics/rules'
|
68
|
-
require 'jirametrics/grouping_rules'
|
69
|
-
require 'jirametrics/daily_wip_chart'
|
70
|
-
require 'jirametrics/groupable_issue_chart'
|
71
|
-
require 'jirametrics/css_variable'
|
72
|
-
require 'jirametrics/issue_collection'
|
73
|
-
|
74
|
-
require 'jirametrics/aggregate_config'
|
75
|
-
require 'jirametrics/expedited_chart'
|
76
|
-
require 'jirametrics/board_config'
|
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
|
122
71
|
end
|
123
72
|
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.15'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bowler
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-08
|
10
|
+
date: 2025-09-08 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: random-word
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- lib/jirametrics/html/hierarchy_table.erb
|
114
114
|
- lib/jirametrics/html/index.css
|
115
115
|
- lib/jirametrics/html/index.erb
|
116
|
+
- lib/jirametrics/html/index.js
|
116
117
|
- lib/jirametrics/html/sprint_burndown.erb
|
117
118
|
- lib/jirametrics/html/throughput_chart.erb
|
118
119
|
- lib/jirametrics/html_report_config.rb
|