jirametrics 2.6 → 2.12pre9
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/aggregate_config.rb +9 -4
- data/lib/jirametrics/aging_work_bar_chart.rb +13 -11
- data/lib/jirametrics/aging_work_in_progress_chart.rb +105 -41
- data/lib/jirametrics/aging_work_table.rb +54 -7
- data/lib/jirametrics/blocked_stalled_change.rb +1 -1
- data/lib/jirametrics/board.rb +47 -13
- data/lib/jirametrics/board_config.rb +7 -2
- data/lib/jirametrics/board_movement_calculator.rb +155 -0
- data/lib/jirametrics/change_item.rb +19 -8
- data/lib/jirametrics/chart_base.rb +63 -27
- data/lib/jirametrics/css_variable.rb +1 -1
- data/lib/jirametrics/cycletime_config.rb +59 -8
- data/lib/jirametrics/cycletime_histogram.rb +68 -3
- data/lib/jirametrics/cycletime_scatterplot.rb +3 -6
- data/lib/jirametrics/daily_wip_by_age_chart.rb +2 -4
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +2 -2
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +0 -4
- data/lib/jirametrics/daily_wip_chart.rb +7 -9
- data/lib/jirametrics/data_quality_report.rb +219 -41
- data/lib/jirametrics/dependency_chart.rb +3 -4
- data/lib/jirametrics/download_config.rb +2 -2
- data/lib/jirametrics/downloader.rb +57 -37
- data/lib/jirametrics/estimate_accuracy_chart.rb +35 -12
- data/lib/jirametrics/estimation_configuration.rb +25 -0
- data/lib/jirametrics/examples/aggregated_project.rb +3 -6
- data/lib/jirametrics/examples/standard_project.rb +14 -13
- data/lib/jirametrics/expedited_chart.rb +7 -8
- data/lib/jirametrics/exporter.rb +28 -13
- data/lib/jirametrics/file_config.rb +23 -6
- data/lib/jirametrics/file_system.rb +41 -4
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
- data/lib/jirametrics/groupable_issue_chart.rb +1 -3
- data/lib/jirametrics/html/aging_work_bar_chart.erb +3 -12
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +22 -5
- data/lib/jirametrics/html/aging_work_table.erb +6 -4
- data/lib/jirametrics/html/cycletime_histogram.erb +74 -0
- data/lib/jirametrics/html/cycletime_scatterplot.erb +1 -10
- data/lib/jirametrics/html/daily_wip_chart.erb +1 -10
- data/lib/jirametrics/html/expedited_chart.erb +1 -10
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
- data/lib/jirametrics/html/hierarchy_table.erb +1 -1
- data/lib/jirametrics/html/index.css +28 -5
- data/lib/jirametrics/html/index.erb +8 -4
- data/lib/jirametrics/html/sprint_burndown.erb +1 -10
- data/lib/jirametrics/html/throughput_chart.erb +1 -10
- data/lib/jirametrics/html_report_config.rb +33 -23
- data/lib/jirametrics/issue.rb +170 -54
- data/lib/jirametrics/jira_gateway.rb +16 -3
- data/lib/jirametrics/project_config.rb +251 -135
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/sprint_burndown.rb +38 -36
- data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
- data/lib/jirametrics/status.rb +81 -26
- data/lib/jirametrics/status_collection.rb +77 -39
- data/lib/jirametrics/throughput_chart.rb +1 -1
- data/lib/jirametrics/value_equality.rb +2 -2
- data/lib/jirametrics.rb +23 -6
- metadata +11 -13
- data/lib/jirametrics/discard_changes_before.rb +0 -37
- data/lib/jirametrics/html/data_quality_report.erb +0 -126
|
@@ -6,7 +6,7 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
6
6
|
{
|
|
7
7
|
type: 'bar',
|
|
8
8
|
data: {
|
|
9
|
-
labels: [<%=
|
|
9
|
+
labels: [<%= @board_columns.collect { |c| c.name.inspect }.join(',') %>],
|
|
10
10
|
datasets: <%= JSON.generate(data_sets) %>
|
|
11
11
|
},
|
|
12
12
|
options: {
|
|
@@ -22,8 +22,10 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
22
22
|
labelString: 'Date Completed'
|
|
23
23
|
},
|
|
24
24
|
grid: {
|
|
25
|
-
color: <%= CssVariable['--grid-line-color'].to_json
|
|
25
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>,
|
|
26
|
+
z: 1 // draw the grid lines on top of the bars
|
|
26
27
|
},
|
|
28
|
+
stacked: true
|
|
27
29
|
},
|
|
28
30
|
y: {
|
|
29
31
|
scaleLabel: {
|
|
@@ -35,8 +37,11 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
35
37
|
text: 'Age in days'
|
|
36
38
|
},
|
|
37
39
|
grid: {
|
|
38
|
-
color: <%= CssVariable['--grid-line-color'].to_json
|
|
40
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>,
|
|
41
|
+
z: 1 // draw the grid lines on top of the bars
|
|
39
42
|
},
|
|
43
|
+
stacked: true,
|
|
44
|
+
max: <%= (@max_age * 1.1).to_i %>
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
plugins: {
|
|
@@ -44,14 +49,26 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
|
|
|
44
49
|
callbacks: {
|
|
45
50
|
label: function(context) {
|
|
46
51
|
if( typeof(context.dataset.data[context.dataIndex]) == "number" ) {
|
|
47
|
-
|
|
52
|
+
let full_data = <%= @bar_data.inspect %>;
|
|
53
|
+
let columnIndex = context.dataIndex;
|
|
54
|
+
let rowIndex = context.datasetIndex - <%= @row_index_offset %>;
|
|
55
|
+
return context.dataset.label + " of completed work items left this column in " +full_data[rowIndex][columnIndex] + " days or less";
|
|
48
56
|
}
|
|
49
57
|
else {
|
|
50
|
-
return context.dataset.data[context.dataIndex].title
|
|
58
|
+
return context.dataset.data[context.dataIndex].title;
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
}
|
|
62
|
+
},
|
|
63
|
+
legend: {
|
|
64
|
+
labels: {
|
|
65
|
+
filter: function(item, chart) {
|
|
66
|
+
// Logic to remove a particular legend item goes here
|
|
67
|
+
return !item.text.includes('%');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
54
70
|
}
|
|
71
|
+
|
|
55
72
|
}
|
|
56
73
|
}
|
|
57
74
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<table class='standard'>
|
|
2
2
|
<thead>
|
|
3
3
|
<tr>
|
|
4
|
-
<th
|
|
5
|
-
<th>E</th>
|
|
6
|
-
<th>B</th>
|
|
4
|
+
<th title="Age in days">Age</th>
|
|
5
|
+
<th title="Expedited">E</th>
|
|
6
|
+
<th title="Blocked / Stalled">B/S</th>
|
|
7
7
|
<th>Issue</th>
|
|
8
8
|
<th>Status</th>
|
|
9
|
+
<th>Forecast</th>
|
|
9
10
|
<th>Fix versions</th>
|
|
10
11
|
<% if any_scrum_boards %>
|
|
11
12
|
<th>Sprints</th>
|
|
@@ -40,7 +41,8 @@
|
|
|
40
41
|
</div>
|
|
41
42
|
<% end %>
|
|
42
43
|
</td>
|
|
43
|
-
<td><%= format_status issue.status
|
|
44
|
+
<td><%= format_status issue.status, board: issue.board %></td>
|
|
45
|
+
<td><%= dates_text(issue) %></td>
|
|
44
46
|
<td><%= fix_versions_text(issue) %></td>
|
|
45
47
|
<% if any_scrum_boards %>
|
|
46
48
|
<td><%= sprints_text(issue) %></td>
|
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
<div class="chart">
|
|
2
2
|
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
3
3
|
</div>
|
|
4
|
+
<%
|
|
5
|
+
if show_stats
|
|
6
|
+
link_id = next_id
|
|
7
|
+
issues_id = next_id
|
|
8
|
+
%>
|
|
9
|
+
[<a id='<%= link_id %>' href="#" onclick='expand_collapse("<%= link_id %>", "<%= issues_id %>"); return false;'>Show details</a>]
|
|
10
|
+
<div id="<%= issues_id %>" style="display: none;">
|
|
11
|
+
<div>
|
|
12
|
+
<table class="standard">
|
|
13
|
+
<tr>
|
|
14
|
+
<th>Issue Type</th>
|
|
15
|
+
<th>Min</th>
|
|
16
|
+
<th>Max</th>
|
|
17
|
+
<th>Avg</th>
|
|
18
|
+
<th>Mode</th>
|
|
19
|
+
<% percentiles.each do |p| %>
|
|
20
|
+
<th><%= p %>th</th>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tr>
|
|
23
|
+
<% the_stats.each do |k, v| %>
|
|
24
|
+
<tr>
|
|
25
|
+
<td><%= k %></td>
|
|
26
|
+
<td style="text-align: right;"><%= v[:min] %></td>
|
|
27
|
+
<td style="text-align: right;"><%= v[:max] %></td>
|
|
28
|
+
<td style="text-align: right;"><%= sprintf('%.2f', v[:average]) %></td>
|
|
29
|
+
<td><%= v[:mode].join(', ') %></td>
|
|
30
|
+
<% percentiles.each do |p| %>
|
|
31
|
+
<td style="text-align: right;"><%= v[:percentiles][p] %></td>
|
|
32
|
+
<% end %>
|
|
33
|
+
</tr>
|
|
34
|
+
<% end %>
|
|
35
|
+
</table>
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<p>These statistics help understand the <i>"shape"</i> of the cycletime histogram distribution, to help us with predictions.</p>
|
|
39
|
+
<ul>
|
|
40
|
+
<li><b>Min & Max:</b> the observed spread for the data set. Useful to judge how wide the variation is. </li>
|
|
41
|
+
<li><b>Average:</b> the arithmetic mean of the data set. Useful as a <i>"typical representative"</i> of the complete set.</li>
|
|
42
|
+
<li><b>Mode:</b> the most repeated value(s) in the data set. This is the value we're most likely to remember. </li>
|
|
43
|
+
<li><b>Percentiles:</b> they partition the data set. If X is the Nth percentile, it means that N% of cycletime values are X or less. Typical percentiles of interest are:</li>
|
|
44
|
+
<ul>
|
|
45
|
+
<li><b>50%</b>: also known as the <b>Median</b>. Useful to establish short feedback loops, to monitor that it's not drifting to the right.</li>
|
|
46
|
+
<li><b>85%</b>: useful to establish service level expectations, accounting for rare events..</li>
|
|
47
|
+
<li><b>98% (or higher)</b>: useful to gauge worst case expectations..</li>
|
|
48
|
+
</ul>
|
|
49
|
+
</ul>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<%
|
|
53
|
+
end
|
|
54
|
+
%>
|
|
4
55
|
<script>
|
|
5
56
|
new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
6
57
|
{
|
|
@@ -21,6 +72,8 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
21
72
|
grid: {
|
|
22
73
|
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
23
74
|
},
|
|
75
|
+
min: 0,
|
|
76
|
+
offset: false, // Gets rid of the ugly padding on left.
|
|
24
77
|
},
|
|
25
78
|
y: {
|
|
26
79
|
stacked: true,
|
|
@@ -34,6 +87,27 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
34
87
|
}
|
|
35
88
|
},
|
|
36
89
|
plugins: {
|
|
90
|
+
annotation: {
|
|
91
|
+
annotations: {
|
|
92
|
+
<%
|
|
93
|
+
results = the_stats[:all][:percentiles]
|
|
94
|
+
results.each do |percentile, value|
|
|
95
|
+
%>
|
|
96
|
+
percentile<%= percentile.to_s %>: {
|
|
97
|
+
type: 'line',
|
|
98
|
+
scaleID: 'x',
|
|
99
|
+
value: <%= value %>,
|
|
100
|
+
borderWidth: 1,
|
|
101
|
+
drawTime: 'beforeDatasetsDraw',
|
|
102
|
+
label: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
content: '<%= "#{percentile}%" %>',
|
|
105
|
+
position: 'start',
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
<% end %>
|
|
109
|
+
},
|
|
110
|
+
},
|
|
37
111
|
tooltip: {
|
|
38
112
|
callbacks: {
|
|
39
113
|
label: function(context) {
|
|
@@ -53,16 +53,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
53
53
|
autocolors: false,
|
|
54
54
|
annotation: {
|
|
55
55
|
annotations: {
|
|
56
|
-
|
|
57
|
-
holiday<%= index %>: {
|
|
58
|
-
drawTime: 'beforeDraw',
|
|
59
|
-
type: 'box',
|
|
60
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
61
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
62
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
63
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
64
|
-
},
|
|
65
|
-
<% end %>
|
|
56
|
+
<%= working_days_annotation %>
|
|
66
57
|
|
|
67
58
|
<% @percentage_lines.each_with_index do |args, index| %>
|
|
68
59
|
<% percent, color = args %>
|
|
@@ -50,16 +50,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
50
50
|
},
|
|
51
51
|
annotation: {
|
|
52
52
|
annotations: {
|
|
53
|
-
|
|
54
|
-
holiday<%= index %>: {
|
|
55
|
-
drawTime: 'beforeDraw',
|
|
56
|
-
type: 'box',
|
|
57
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
58
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
59
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
60
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
61
|
-
},
|
|
62
|
-
<% end %>
|
|
53
|
+
<%= working_days_annotation %>
|
|
63
54
|
}
|
|
64
55
|
},
|
|
65
56
|
legend: {
|
|
@@ -55,16 +55,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
55
55
|
autocolors: false,
|
|
56
56
|
annotation: {
|
|
57
57
|
annotations: {
|
|
58
|
-
|
|
59
|
-
holiday<%= index %>: {
|
|
60
|
-
drawTime: 'beforeDraw',
|
|
61
|
-
type: 'box',
|
|
62
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
63
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
64
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
65
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
66
|
-
},
|
|
67
|
-
<% end %>
|
|
58
|
+
<%= working_days_annotation %>
|
|
68
59
|
}
|
|
69
60
|
}
|
|
70
61
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<div class="chart">
|
|
2
|
+
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
3
|
+
</div>
|
|
4
|
+
<script>
|
|
5
|
+
new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
6
|
+
type: 'scatter',
|
|
7
|
+
data: {
|
|
8
|
+
datasets: <%= JSON.generate(data_sets) %>
|
|
9
|
+
},
|
|
10
|
+
options: {
|
|
11
|
+
title: {
|
|
12
|
+
display: true,
|
|
13
|
+
text: "Cycletime Scatterplot"
|
|
14
|
+
},
|
|
15
|
+
responsive: <%= canvas_responsive? %>, // If responsive is true then it fills the screen
|
|
16
|
+
scales: {
|
|
17
|
+
x: {
|
|
18
|
+
scaleLabel: {
|
|
19
|
+
display: true,
|
|
20
|
+
labelString: 'Days'
|
|
21
|
+
},
|
|
22
|
+
title: {
|
|
23
|
+
display: true,
|
|
24
|
+
text: 'Total time (days)'
|
|
25
|
+
},
|
|
26
|
+
grid: {
|
|
27
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
},
|
|
31
|
+
y: {
|
|
32
|
+
scaleLabel: {
|
|
33
|
+
display: true,
|
|
34
|
+
labelString: 'Percentage',
|
|
35
|
+
min: 0,
|
|
36
|
+
max: <%= @highest_cycletime %>
|
|
37
|
+
},
|
|
38
|
+
title: {
|
|
39
|
+
display: true,
|
|
40
|
+
text: 'Time adding value (days)'
|
|
41
|
+
},
|
|
42
|
+
grid: {
|
|
43
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
plugins: {
|
|
48
|
+
tooltip: {
|
|
49
|
+
callbacks: {
|
|
50
|
+
label: function(context) {
|
|
51
|
+
return context.dataset.data[context.dataIndex].title
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
autocolors: false,
|
|
56
|
+
legend: {
|
|
57
|
+
onClick: (evt, legendItem, legend) => {
|
|
58
|
+
// Find the datasetMeta that corresponds to the item clicked
|
|
59
|
+
var i = 0
|
|
60
|
+
while(legendItem.text != legend.chart.getDatasetMeta(i).label) {
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
nextVisibility = !!legend.chart.getDatasetMeta(i).hidden;
|
|
64
|
+
|
|
65
|
+
// Hide/show the 85% line for that dataset
|
|
66
|
+
legend.chart.options.plugins.annotation.annotations["line"+(i/2)].display = nextVisibility;
|
|
67
|
+
|
|
68
|
+
// Hide/show the trendline for this dataset, if they were enabled. The trendline is always
|
|
69
|
+
// there but not always visible.
|
|
70
|
+
legend.chart.setDatasetVisibility(i+1, <%= !!@show_trend_lines %> && nextVisibility);
|
|
71
|
+
|
|
72
|
+
// Still run the default behaviour
|
|
73
|
+
Chart.defaults.plugins.legend.onClick(evt, legendItem, legend);
|
|
74
|
+
},
|
|
75
|
+
labels: {
|
|
76
|
+
filter: function(item, chart) {
|
|
77
|
+
// Logic to remove a particular legend item goes here
|
|
78
|
+
return !item.text.includes('Trendline');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</span>
|
|
23
23
|
</td>
|
|
24
24
|
<td><span style="color: <%= color %>; font-style: italic;"><%= issue.summary[0..80] %></span></td>
|
|
25
|
-
<td><%= format_status issue.status
|
|
25
|
+
<td><%= format_status issue.status, board: issue.board %></td>
|
|
26
26
|
</tr>
|
|
27
27
|
<% end %>
|
|
28
28
|
</tbody>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
--body-background: white;
|
|
3
3
|
--default-text-color: black;
|
|
4
4
|
--grid-line-color: lightgray;
|
|
5
|
+
--warning-banner: yellow;
|
|
5
6
|
|
|
6
7
|
--cycletime-scatterplot-overall-trendline-color: gray;
|
|
7
8
|
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
--status-category-todo-color: gray;
|
|
20
21
|
--status-category-inprogress-color: #2663ff;
|
|
21
22
|
--status-category-done-color: #00ff00;
|
|
23
|
+
--status-category-unknown-color: black;
|
|
22
24
|
|
|
23
25
|
--aging-work-bar-chart-percentage-line-color: red;
|
|
24
26
|
--aging-work-bar-chart-separator-color: white;
|
|
@@ -26,8 +28,16 @@
|
|
|
26
28
|
--throughput_chart_total_line_color: gray;
|
|
27
29
|
|
|
28
30
|
--aging-work-in-progress-chart-shading-color: lightgray;
|
|
31
|
+
--aging-work-in-progress-chart-shading-50-color: #2E8BC0; // green;
|
|
32
|
+
--aging-work-in-progress-chart-shading-85-color: #ADD8E6; // yellow;
|
|
33
|
+
--aging-work-in-progress-chart-shading-98-color: #FF8A8A; // orange;
|
|
34
|
+
--aging-work-in-progress-chart-shading-100-color: #FF2E2E; // red;
|
|
35
|
+
|
|
29
36
|
--aging-work-in-progress-by-age-trend-line-color: gray;
|
|
30
37
|
|
|
38
|
+
--aging-work-table-date-in-jeopardy: yellow;
|
|
39
|
+
--aging-work-table-date-overdue: red;
|
|
40
|
+
|
|
31
41
|
--hierarchy-table-inactive-item-text-color: gray;
|
|
32
42
|
|
|
33
43
|
--wip-chart-completed-color: #00ff00;
|
|
@@ -99,9 +109,6 @@ table.standard {
|
|
|
99
109
|
background-color: #eee;
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
|
-
.quality_note_bullet {
|
|
103
|
-
color: red;
|
|
104
|
-
}
|
|
105
112
|
|
|
106
113
|
.chart {
|
|
107
114
|
background-color: white;
|
|
@@ -119,8 +126,26 @@ div.color_block {
|
|
|
119
126
|
border: 1px solid black;
|
|
120
127
|
}
|
|
121
128
|
|
|
129
|
+
ul.quality_report {
|
|
130
|
+
list-style-type: '⮕';
|
|
131
|
+
::marker {
|
|
132
|
+
color: red;
|
|
133
|
+
}
|
|
134
|
+
li {
|
|
135
|
+
padding: 0.2em;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#footer {
|
|
140
|
+
text-align: center;
|
|
141
|
+
margin-top: 1em;
|
|
142
|
+
border-top: 1px solid gray;
|
|
143
|
+
}
|
|
144
|
+
|
|
122
145
|
@media screen and (prefers-color-scheme: dark) {
|
|
123
146
|
:root {
|
|
147
|
+
--warning-banner: #9F2B00;
|
|
148
|
+
|
|
124
149
|
--non-working-days-color: #2f2f2f;
|
|
125
150
|
--type-story-color: #6fb86f;
|
|
126
151
|
--type-task-color: #0021b3;
|
|
@@ -136,8 +161,6 @@ div.color_block {
|
|
|
136
161
|
--dead-color: black;
|
|
137
162
|
--wip-chart-active-color: #2551c1;
|
|
138
163
|
|
|
139
|
-
--aging-work-in-progress-chart-shading-color: #b4b4b4;
|
|
140
|
-
|
|
141
164
|
--status-category-inprogress-color: #1c49bb;
|
|
142
165
|
|
|
143
166
|
--cycletime-scatterplot-overall-trendline-color: gray;
|
|
@@ -23,16 +23,20 @@
|
|
|
23
23
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
24
24
|
location.reload()
|
|
25
25
|
})
|
|
26
|
-
|
|
27
26
|
</script>
|
|
28
27
|
<style>
|
|
29
28
|
<%= css %>
|
|
30
29
|
</style>
|
|
30
|
+
<script type="text/javascript">
|
|
31
|
+
Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--default-text-color');
|
|
32
|
+
</script>
|
|
31
33
|
</head>
|
|
32
34
|
<body>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
<noscript>
|
|
36
|
+
<div style="padding: 1em; background: gray; color: white; font-size: 2em;">
|
|
37
|
+
Javascript is currently disabled and that means that almost all of the charts in this report won't render. If you'd loaded this from a folder on SharePoint then save it locally and load it again.
|
|
38
|
+
</div>
|
|
39
|
+
</noscript>
|
|
36
40
|
<%= "\n" + @sections.collect { |text, type| text if type == :header }.compact.join("\n\n") %>
|
|
37
41
|
<%= "\n" + @sections.collect { |text, type| text if type == :body }.compact.join("\n\n") %>
|
|
38
42
|
</body>
|
|
@@ -56,16 +56,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
56
56
|
},
|
|
57
57
|
annotation: {
|
|
58
58
|
annotations: {
|
|
59
|
-
|
|
60
|
-
holiday<%= index %>: {
|
|
61
|
-
drawTime: 'beforeDraw',
|
|
62
|
-
type: 'box',
|
|
63
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
64
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
65
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
66
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
67
|
-
},
|
|
68
|
-
<% end %>
|
|
59
|
+
<%= working_days_annotation %>
|
|
69
60
|
}
|
|
70
61
|
}
|
|
71
62
|
}
|
|
@@ -52,16 +52,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
52
52
|
},
|
|
53
53
|
annotation: {
|
|
54
54
|
annotations: {
|
|
55
|
-
|
|
56
|
-
holiday<%= index %>: {
|
|
57
|
-
drawTime: 'beforeDraw',
|
|
58
|
-
type: 'box',
|
|
59
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
60
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
61
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
62
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
63
|
-
},
|
|
64
|
-
<% end %>
|
|
55
|
+
<%= working_days_annotation %>
|
|
65
56
|
}
|
|
66
57
|
}
|
|
67
58
|
}
|
|
@@ -5,16 +5,15 @@ require 'jirametrics/self_or_issue_dispatcher'
|
|
|
5
5
|
|
|
6
6
|
class HtmlReportConfig
|
|
7
7
|
include SelfOrIssueDispatcher
|
|
8
|
-
include DiscardChangesBefore
|
|
9
8
|
|
|
10
|
-
attr_reader :file_config, :sections
|
|
9
|
+
attr_reader :file_config, :sections, :charts
|
|
11
10
|
|
|
12
11
|
def self.define_chart name:, classname:, deprecated_warning: nil, deprecated_date: nil
|
|
13
12
|
lines = []
|
|
14
13
|
lines << "def #{name} &block"
|
|
15
14
|
lines << ' block = ->(_) {} unless block'
|
|
16
15
|
if deprecated_warning
|
|
17
|
-
lines << " deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
|
|
16
|
+
lines << " file_system.deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
|
|
18
17
|
end
|
|
19
18
|
lines << " execute_chart #{classname}.new(block)"
|
|
20
19
|
lines << 'end'
|
|
@@ -33,6 +32,7 @@ class HtmlReportConfig
|
|
|
33
32
|
define_chart name: 'cycletime_histogram', classname: 'CycletimeHistogram'
|
|
34
33
|
define_chart name: 'estimate_accuracy_chart', classname: 'EstimateAccuracyChart'
|
|
35
34
|
define_chart name: 'hierarchy_table', classname: 'HierarchyTable'
|
|
35
|
+
define_chart name: 'flow_efficiency_scatterplot', classname: 'FlowEfficiencyScatterplot'
|
|
36
36
|
|
|
37
37
|
define_chart name: 'daily_wip_by_type', classname: 'DailyWipChart',
|
|
38
38
|
deprecated_warning: 'This is the same as daily_wip_chart. Please use that one', deprecated_date: '2024-05-23'
|
|
@@ -42,14 +42,15 @@ class HtmlReportConfig
|
|
|
42
42
|
def initialize file_config:, block:
|
|
43
43
|
@file_config = file_config
|
|
44
44
|
@block = block
|
|
45
|
-
@sections = []
|
|
45
|
+
@sections = [] # Where we store the chunks of text that will be assembled into the HTML
|
|
46
|
+
@charts = [] # Where we store all the charts we executed so we can assert against them.
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def cycletime label = nil, &block
|
|
49
50
|
@file_config.project_config.all_boards.each_value do |board|
|
|
50
51
|
raise 'Multiple cycletimes not supported' if board.cycletime
|
|
51
52
|
|
|
52
|
-
board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block)
|
|
53
|
+
board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block, file_system: file_system)
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
|
|
@@ -63,9 +64,11 @@ class HtmlReportConfig
|
|
|
63
64
|
|
|
64
65
|
# The quality report has to be generated last because otherwise cycletime won't have been
|
|
65
66
|
# set. Then we have to rotate it to the first position so it's at the top of the report.
|
|
66
|
-
execute_chart DataQualityReport.new(
|
|
67
|
+
execute_chart DataQualityReport.new(file_config.project_config.discarded_changes_data)
|
|
67
68
|
@sections.rotate!(-1)
|
|
68
69
|
|
|
70
|
+
html create_footer
|
|
71
|
+
|
|
69
72
|
html_directory = "#{Pathname.new(File.realpath(__FILE__)).dirname}/html"
|
|
70
73
|
css = load_css html_directory: html_directory
|
|
71
74
|
erb = ERB.new file_system.load(File.join(html_directory, 'index.erb'))
|
|
@@ -98,9 +101,8 @@ class HtmlReportConfig
|
|
|
98
101
|
base_css
|
|
99
102
|
end
|
|
100
103
|
|
|
101
|
-
def board_id id
|
|
102
|
-
@board_id = id
|
|
103
|
-
@board_id
|
|
104
|
+
def board_id id
|
|
105
|
+
@board_id = id
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
def timezone_offset
|
|
@@ -140,19 +142,6 @@ class HtmlReportConfig
|
|
|
140
142
|
end
|
|
141
143
|
end
|
|
142
144
|
|
|
143
|
-
def discard_changes_before_hook issues_cutoff_times
|
|
144
|
-
# raise 'Cycletime must be defined before using discard_changes_before' unless @cycletime
|
|
145
|
-
|
|
146
|
-
@original_issue_times = {}
|
|
147
|
-
issues_cutoff_times.each do |issue, cutoff_time|
|
|
148
|
-
started = issue.board.cycletime.started_time(issue)
|
|
149
|
-
if started && started <= cutoff_time
|
|
150
|
-
# We only need to log this if data was discarded
|
|
151
|
-
@original_issue_times[issue] = { cutoff_time: cutoff_time, started_time: started }
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
145
|
def dependency_chart &block
|
|
157
146
|
execute_chart DependencyChart.new block
|
|
158
147
|
end
|
|
@@ -172,7 +161,7 @@ class HtmlReportConfig
|
|
|
172
161
|
chart.settings = settings
|
|
173
162
|
|
|
174
163
|
chart.all_boards = project_config.all_boards
|
|
175
|
-
chart.board_id = find_board_id
|
|
164
|
+
chart.board_id = find_board_id
|
|
176
165
|
chart.holiday_dates = project_config.exporter.holiday_dates
|
|
177
166
|
|
|
178
167
|
time_range = @file_config.project_config.time_range
|
|
@@ -181,6 +170,7 @@ class HtmlReportConfig
|
|
|
181
170
|
|
|
182
171
|
after_init_block&.call chart
|
|
183
172
|
|
|
173
|
+
@charts << chart
|
|
184
174
|
html chart.run
|
|
185
175
|
end
|
|
186
176
|
|
|
@@ -201,4 +191,24 @@ class HtmlReportConfig
|
|
|
201
191
|
def boards
|
|
202
192
|
@file_config.project_config.board_configs.collect(&:id).collect { |id| find_board id }
|
|
203
193
|
end
|
|
194
|
+
|
|
195
|
+
def create_footer now: DateTime.now
|
|
196
|
+
now = now.new_offset(timezone_offset)
|
|
197
|
+
version = Gem.loaded_specs['jirametrics']&.version || 'Next'
|
|
198
|
+
|
|
199
|
+
<<~HTML
|
|
200
|
+
<section id="footer">
|
|
201
|
+
Report generated on <b>#{now.strftime('%Y-%b-%d')}</b> at <b>#{now.strftime('%I:%M:%S%P %Z')}</b>
|
|
202
|
+
with <a href="https://jirametrics.org">JiraMetrics</a> <b>v#{version}</b>
|
|
203
|
+
</section>
|
|
204
|
+
HTML
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def discard_changes_before status_becomes: nil, &block
|
|
208
|
+
file_system.deprecated(
|
|
209
|
+
date: '2025-01-09',
|
|
210
|
+
message: 'discard_changes_before is now only supported at the project level'
|
|
211
|
+
)
|
|
212
|
+
file_config.project_config.discard_changes_before status_becomes: status_becomes, &block
|
|
213
|
+
end
|
|
204
214
|
end
|