Commit 5246525b authored by Antonio Terceiro's avatar Antonio Terceiro

Rewrite Status charts page

parent ccd910ad
......@@ -5,7 +5,7 @@
: i.e. a package fails in some architectures and passes on others
[_] UI improvements
[_] reduce size of JSON data download for search in home page
[_] reduce size of JSON data download for charts in status page
[X] reduce size of JSON data download for charts in status page
[_] qemu support
[_] add qemu backend
[_] test qemu
......
......@@ -13,6 +13,9 @@ debci (1.1.3) UNRELEASED; urgency=medium
one suite/arch but fails on another suite/arch.
* fix Status page to not hide all charts when any platform does not have
enough data
* rewrite Status charts page. In special, instead of loading the full
history via AJAX, which takes quite a while, embed a reduced view of the
data in the HTML itself.
* Drop debian/tests/worker-max-processes. The original issue was caused by
(and fixed in) systemd >= 228 (see bug #823530), and the test ended up not
being very reliable and keeps flapping on CI.
......
......@@ -8,7 +8,7 @@ module Debci
# suite and architecture.
class Graph
attr_accessor :date, :pass, :fail, :tmpfail, :total, :pass_percentage
attr_accessor :suite, :architecture, :entries
def initialize(repository, suite, architecture)
@repository = repository
......@@ -17,31 +17,21 @@ module Debci
load_data
end
# Returns the value of the last data entry for the specified field
def current_value(field)
data = send(field)
data[-1] || 0
end
# Returns the value of the second to last data entry for the
# specified field
def previous_value(field)
data = send(field)
data[-2] || 0
end
private
# Read the status data
def load_data
data = @repository.status_history(@suite, @architecture)
return unless data
self.date = data.map { |entry| Time.parse(entry['date'] + ' UTC') }
self.pass = data.map { |entry| entry['pass'] }
self.fail = data.map { |entry| entry['fail'] }
self.tmpfail = data.map { |entry| entry['tmpfail'] ? entry['tmpfail'] : 0 }
self.total = data.map { |entry| entry['total'] }
self.pass_percentage = data.map { |entry| entry['pass'].to_f / entry['total'].to_f }
# load all the data
@entries = @repository.status_history(@suite, @architecture)
return unless @entries
return if @entries.size <= 100
# simplify the data: pick 101 points in the history
original_entries = @entries
@entries = (0..100).map do |i|
j = (i * (original_entries.size-1).to_f / 100).round
@entries[j]
end
end
end
......
......@@ -2,90 +2,50 @@
<div class="row">
<div class="col-md-12">
<h1 class='page-header'>Status <small>/ Charts</small></h1>
<%= @status_nav %>
<h1 class='page-header'>Status <small>/ Charts</small></h1>
<%= @status_nav %>
<div id='status'>
<% @repository.architectures.each do |arch| %>
<% @repository.suites.each do |suite| %>
<% chart = Debci::Graph.new(@repository, suite, arch) %>
<meta name='data-<%= suite %>-<%= arch %>' content='<%= chart.entries.to_json%>'/>
<h3><%= "#{suite}/#{arch}" %></h3>
<noscript>
<table>
<tr>
<th>Date</th>
<th>Pass</th>
<th>Fail</th>
<th>Temporary failure</th>
<th>Pass percentage</th>
</tr>
<% chart.entries.each do |entry| %>
<tr>
<td><%= entry['date'] %></td>
<td><%= entry['pass'] %></td>
<td><%= entry['fail'] %></td>
<td><%= entry['tmpfail'] %></td>
<td><%= 100 * entry['pass'].to_f / entry['total'].to_f %>%</td>
</tr>
<% end %>
</table>
</noscript>
<div class='arch-charts row'>
<div class='chart col-md-4'>
<h4>Pass/Fail</h4>
<div class='chart-canvas' id='chart-pass-fail-<%= suite %>-<%= arch %>'></div>
</div>
<div class='chart col-md-4'>
<h4>Pass percentage</h4>
<div class='chart-canvas' id='chart-pass-percentage-<%= suite %>-<%= arch %>'></div>
</div>
</div>
<div id='status'>
<h3 class='page-header'>Pass/Fail</h3>
<% @repository.architectures.each do |arch| %>
<div class='row'>
<% @repository.suites.each do |suite| %>
<div class='col-md-4'>
<div class='text-center'><h4><%= "#{suite}/#{arch}" %></h4></div>
<% if @repository.status_history(suite, arch) %>
<noscript>
<% chart = Debci::Graph.new(@repository, suite, arch) %>
<table class='table text-center'>
<tr>
<td><%= chart.previous_value('date') %></td>
<td><%= chart.current_value('date') %></td>
</tr>
<tr>
<td class='pass'><%= chart.previous_value('pass') %></td>
<td class='pass'><%= chart.current_value('pass') %></td>
</tr>
<tr>
<td class='fail'><%= chart.previous_value('fail') %></td>
<td class='fail'><%= chart.current_value('fail') %></td>
</tr>
<tr>
<td class='tmpfail'><%= chart.previous_value('tmpfail') %></td>
<td class='tmpfail'><%= chart.current_value('tmpfail') %></td>
</tr>
</table>
</noscript>
<div class='chart chart-<%= suite %>-<%= arch%>' id='chart-pass-fail<%= suite %>-<%= arch %>'></div>
<% else %>
<h4 class='text-center'>No data available</h4>
<% end %>
</div>
<% end %>
</div>
<% end %>
<br><br>
<h3 class='page-header'>Pass Percentage</h3>
<% @repository.architectures.each do |arch| %>
<div class='row'>
<% @repository.suites.each do |suite| %>
<div class='col-md-4'>
<div class='text-center'><h4><%= "#{suite}/#{arch}" %></h4></div>
<% if @repository.status_history(suite, arch) %>
<noscript>
<% chart = Debci::Graph.new(@repository, suite, arch) %>
<table class='table text-center'>
<tr>
<td><%= chart.previous_value('date') %></td>
<td><%= chart.current_value('date') %></td>
</tr>
<tr>
<td class='pass'><%= (chart.previous_value('pass_percentage') * 100).round(2) %>%</td>
<td class='pass'><%= (chart.current_value('pass_percentage') * 100).round(2) %>%</td>
</tr>
</table>
</noscript>
<div class='chart chart-<%= suite %>-<%= arch%>' id='chart-pass-percentage<%= suite %>-<%= arch %>'></div>
<% else %>
<h4 class='text-center'>No data available</h4>
<% end %>
</div>
<% end %>
</div>
<% end %>
<% end %>
</div>
</div>
</div> <!-- class='col-md-12' -->
</div> <!-- class='row -->
</div> <!-- class='container' -->
jQuery(function($) {
var PACKAGES_HTML_DIR = '/data/.html/packages';
var STATUS_DIR = '/data/status/';
var STATUS_HTML_DIR = '/data/.html/status';
function pkg_dir(pkg) {
return pkg.replace(/^((lib)?.)/, "$1/$&");
......@@ -15,93 +13,89 @@ jQuery(function($) {
}
on('#status', function() {
$.get(STATUS_HTML_DIR + '/platforms.json', function(data) {
$.each(data, function(index, item) {
var platform = item.platform.replace('/', '-')
$.get(STATUS_DIR + item.platform + '/history.json', function(data) {
if (data.length < 2) {
$('.chart-' + item.platform.replace('/', '-')).html("Not enough data for plot. Wait until we get more data.");
return;
$('meta[name*=data-]').each(function() {
var platform = $(this).attr('name').replace('data-', '');
var json = $('meta[name=data-' + platform + ']').attr("content");
var data = $.parseJSON(json);
console.log(platform + ' data: ' + data);
if (data.length < 2) {
$('.chart-' + platform).html("Not enough data for plot. Wait until we get more data.");
console.log('skipping ' + platform + '...')
} else {
var pass = [];
var fail = [];
var tmpfail = [];
var pass_percentage = [];
var duration = [];
var max_duration = 0;
$.each(data, function(index, entry) {
var date = Date.parse(entry.date);
pass.push([date, entry.pass]);
fail.push([date, entry.fail]);
tmpfail.push([date, entry.tmpfail || 0]);
pass_percentage.push([date, entry.pass / entry.total]);
duration.push([date, entry.duration]);
if (entry.duration && entry.duration > max_duration) {
max_duration = entry.duration;
}
});
var pass = [];
var fail = [];
var tmpfail = [];
var pass_percentage = [];
var duration = [];
var max_duration = 0;
$.each(data, function(index, entry) {
var date = Date.parse(entry.date);
pass.push([date, entry.pass]);
fail.push([date, entry.fail]);
tmpfail.push([date, entry.tmpfail || 0]);
pass_percentage.push([date, entry.pass / entry.total]);
duration.push([date, entry.duration]);
if (entry.duration && entry.duration > max_duration) {
max_duration = entry.duration;
}
});
var status_data = [
{
label: "Pass",
data: pass
},
{
label: "Fail",
data: fail
},
{
label: "Temporary failure",
data: tmpfail
}
];
$.plot("#chart-pass-fail" + platform, status_data, {
series: {
stack: true,
lines: {
show: true,
fill: true,
steps: false,
}
},
colors: [ '#8ae234', '#ef2929', '#ffd862' ],
legend: {
var status_data = [
{
label: "Pass",
data: pass
},
{
label: "Fail",
data: fail
},
{
label: "Temporary failure",
data: tmpfail
}
];
$.plot("#chart-pass-fail-" + platform, status_data, {
series: {
stack: true,
lines: {
show: true,
backgroundOpacity: 0.2,
position: 'sw'
},
xaxis: {
mode: "time"
},
yaxis: {
min: 0
fill: true,
steps: false,
}
});
$.plot('#chart-pass-percentage' + platform, [pass_percentage], {
series: {
lines: {
show: true
}
},
colors: [ '#8ae234' ],
xaxis: {
mode: 'time',
},
yaxis: {
min: 0,
max: 1,
ticks: [[0.25, '25%'], [0.5, '50%'], [0.75, '75%'], [1.0, '100%']]
}
});
},
colors: [ '#8ae234', '#ef2929', '#ffd862' ],
legend: {
show: true,
backgroundOpacity: 0.2,
position: 'sw'
},
xaxis: {
mode: "time"
},
yaxis: {
min: 0
}
});
})
})
})
$.plot('#chart-pass-percentage-' + platform, [pass_percentage], {
series: {
lines: {
show: true
}
},
colors: [ '#8ae234' ],
xaxis: {
mode: 'time',
},
yaxis: {
min: 0,
max: 1,
ticks: [[0.25, '25%'], [0.5, '50%'], [0.75, '75%'], [1.0, '100%']]
}
});
}
});
});
on('#package-select', function() {
......
#status h3 {
border-bottom: 1px solid #888a85;
}
.arch-charts {
height: 250px;
}
.chart {
height: 180px;
width: 100%;
}
.chart h4 {
text-align: center;
}
.chart-canvas {
width: 100%;
height: 200px;
}
#status-nav {
......
......@@ -30,26 +30,19 @@ describe Debci::Graph do
let(:repository) { Debci::Repository.new(@datadir) }
let(:graph) { Debci::Graph.new(repository, 'unstable', 'amd64') }
it 'returns the current/last value for a set of data' do
expect(graph.current_value('date')).to eq(Time.parse('2014-08-15 01:30:15' + ' UTC'))
expect(graph.current_value('pass')).to eq(200)
expect(graph.current_value('fail')).to eq(150)
expect(graph.current_value('tmpfail')).to eq(20)
expect(graph.current_value('total')).to eq(370)
it 'gets history snapshots as entries' do
expect(graph.entries.size).to eq(2)
end
it 'returns the second to last value for a set of data' do
expect(graph.previous_value('date')).to eq(Time.parse('2014-08-10 12:12:30' + ' UTC'))
expect(graph.previous_value('pass')).to eq(100)
expect(graph.previous_value('fail')).to eq(200)
expect(graph.previous_value('tmpfail')).to eq(20)
expect(graph.previous_value('total')).to eq(320)
end
it 'reduces history do 101 entries' do
initial_date = Time.parse('2014-08-10 12:12:30 UTC')
data = (0..150).map do |i|
{ 'date' => initial_date + 3600*24*i, 'pass' => 100, 'fail' => 200, 'tmpfail' => 20, 'total' => 320 }
end
history 'status/unstable/amd64', data
it 'gets status history data' do
expect(graph.pass).to include(100, 200)
expect(graph.fail).to include(200, 150)
expect(graph.tmpfail).to include(20, 20)
expect(graph.total).to include(320, 370)
expect(graph.entries.size).to eq(101)
expect(Time.parse(graph.entries.first['date'])).to eq(initial_date)
expect(Time.parse(graph.entries.last['date'])).to eq(initial_date + 150*24*3600)
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment