jirametrics 2.4 → 2.11
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 +44 -15
- data/lib/jirametrics/board_config.rb +7 -3
- data/lib/jirametrics/board_movement_calculator.rb +147 -0
- data/lib/jirametrics/change_item.rb +19 -6
- 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 +37 -10
- data/lib/jirametrics/download_config.rb +12 -0
- data/lib/jirametrics/downloader.rb +68 -50
- data/lib/jirametrics/estimate_accuracy_chart.rb +1 -2
- data/lib/jirametrics/examples/aggregated_project.rb +7 -21
- data/lib/jirametrics/examples/standard_project.rb +18 -34
- data/lib/jirametrics/expedited_chart.rb +8 -9
- data/lib/jirametrics/exporter.rb +28 -11
- data/lib/jirametrics/file_config.rb +23 -6
- data/lib/jirametrics/file_system.rb +39 -3
- 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 +232 -47
- data/lib/jirametrics/jira_gateway.rb +16 -3
- data/lib/jirametrics/project_config.rb +245 -134
- data/lib/jirametrics/rules.rb +2 -2
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/settings.json +5 -2
- data/lib/jirametrics/sprint_burndown.rb +3 -3
- data/lib/jirametrics/status.rb +84 -19
- 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 +22 -6
- metadata +10 -13
- data/lib/jirametrics/discard_changes_before.rb +0 -37
- data/lib/jirametrics/html/data_quality_report.erb +0 -126
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jirametrics/groupable_issue_chart'
|
|
4
|
+
|
|
5
|
+
class FlowEfficiencyScatterplot < ChartBase
|
|
6
|
+
include GroupableIssueChart
|
|
7
|
+
|
|
8
|
+
attr_accessor :possible_statuses
|
|
9
|
+
|
|
10
|
+
def initialize block
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
header_text 'Flow Efficiency'
|
|
14
|
+
description_text <<-HTML
|
|
15
|
+
<div class="p">
|
|
16
|
+
This chart shows the active time against the the total time spent on a ticket.
|
|
17
|
+
<a href="https://improvingflow.com/2024/07/06/flow-efficiency.html">Flow efficiency</a> is the ratio
|
|
18
|
+
between these two numbers.
|
|
19
|
+
</div>
|
|
20
|
+
<div class="p">
|
|
21
|
+
<math>
|
|
22
|
+
<mn>Flow efficiency (%)</mn>
|
|
23
|
+
<mo>=</mo>
|
|
24
|
+
<mfrac>
|
|
25
|
+
<mrow><mn>Time adding value</mn></mrow>
|
|
26
|
+
<mrow><mn>Total time</mn></mrow>
|
|
27
|
+
</mfrac>
|
|
28
|
+
</math>
|
|
29
|
+
</div>
|
|
30
|
+
<div style="background: var(--warning-banner)">Note that for this calculation to be accurate, we must be moving items into a
|
|
31
|
+
blocked or stalled state the moment we stop working on it, and most teams don't do that.
|
|
32
|
+
So be aware that your team may have to change their behaviours if you want this chart to be useful.
|
|
33
|
+
</div>
|
|
34
|
+
HTML
|
|
35
|
+
|
|
36
|
+
init_configuration_block block do
|
|
37
|
+
grouping_rules do |issue, rule|
|
|
38
|
+
active_time, total_time = issue.flow_efficiency_numbers end_time: time_range.end
|
|
39
|
+
flow_efficiency = active_time * 100.0 / total_time
|
|
40
|
+
|
|
41
|
+
if flow_efficiency > 99.0
|
|
42
|
+
rule.label = '~100%'
|
|
43
|
+
rule.color = 'green'
|
|
44
|
+
elsif flow_efficiency < 30.0
|
|
45
|
+
rule.label = '< 30%'
|
|
46
|
+
rule.color = 'orange'
|
|
47
|
+
else
|
|
48
|
+
rule.label = 'The rest'
|
|
49
|
+
rule.color = 'black'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@percentage_lines = []
|
|
55
|
+
@highest_cycletime = 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run
|
|
59
|
+
data_sets = group_issues(completed_issues_in_range include_unstarted: false).filter_map do |rules, issues|
|
|
60
|
+
create_dataset(issues: issues, label: rules.label, color: rules.color)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if data_sets.empty?
|
|
64
|
+
|
|
65
|
+
wrap_and_render(binding, __FILE__)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_days seconds
|
|
69
|
+
seconds / 60 / 60 / 24
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def create_dataset issues:, label:, color:
|
|
73
|
+
return nil if issues.empty?
|
|
74
|
+
|
|
75
|
+
data = issues.filter_map do |issue|
|
|
76
|
+
active_time, total_time = issue.flow_efficiency_numbers(
|
|
77
|
+
end_time: time_range.end, settings: settings
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
active_days = to_days(active_time)
|
|
81
|
+
total_days = to_days(total_time)
|
|
82
|
+
flow_efficiency = active_time * 100.0 / total_time
|
|
83
|
+
|
|
84
|
+
if flow_efficiency.nan?
|
|
85
|
+
# If this happens then something is probably misconfigured. We've seen it in production though
|
|
86
|
+
# so we have to handle it.
|
|
87
|
+
file_system.log(
|
|
88
|
+
"Issue(#{issue.key}) flow_efficiency: NaN, active_time: #{active_time}, total_time: #{total_time}"
|
|
89
|
+
)
|
|
90
|
+
flow_efficiency = 0.0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
y: active_days,
|
|
95
|
+
x: total_days,
|
|
96
|
+
title: [
|
|
97
|
+
"#{issue.key} : #{issue.summary}, flow efficiency: #{flow_efficiency.to_i}%," \
|
|
98
|
+
" total: #{total_days.round(1)} days," \
|
|
99
|
+
" active: #{active_days.round(1)} days"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
{
|
|
104
|
+
label: label,
|
|
105
|
+
data: data,
|
|
106
|
+
fill: false,
|
|
107
|
+
showLine: false,
|
|
108
|
+
backgroundColor: color
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -6,9 +6,7 @@ require 'jirametrics/grouping_rules'
|
|
|
6
6
|
module GroupableIssueChart
|
|
7
7
|
def init_configuration_block user_provided_block, &default_block
|
|
8
8
|
instance_eval(&user_provided_block)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
instance_eval(&default_block)
|
|
9
|
+
instance_eval(&default_block) unless @group_by_block
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
def grouping_rules &block
|
|
@@ -38,22 +38,13 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
|
|
|
38
38
|
plugins: {
|
|
39
39
|
annotation: {
|
|
40
40
|
annotations: {
|
|
41
|
-
|
|
42
|
-
holiday<%= index %>: {
|
|
43
|
-
drawTime: 'beforeDraw',
|
|
44
|
-
type: 'box',
|
|
45
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
46
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
47
|
-
backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
|
|
48
|
-
borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
|
|
49
|
-
},
|
|
50
|
-
<% end %>
|
|
41
|
+
<%= working_days_annotation %>
|
|
51
42
|
|
|
52
43
|
<% if percentage_line_x %>
|
|
53
44
|
line: {
|
|
54
45
|
type: 'line',
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
scaleID: 'x',
|
|
47
|
+
value: '<%= percentage_line_x %>',
|
|
57
48
|
borderColor: <%= CssVariable.new('--aging-work-bar-chart-percentage-line-color').to_json %>,
|
|
58
49
|
borderWidth: 1,
|
|
59
50
|
drawTime: 'afterDraw'
|
|
@@ -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
|
}
|