First attempt at adding a graph to job detail page.

This commit is contained in:
chouseknecht 2014-05-15 03:51:34 -04:00
parent 69aa991499
commit a895c6beea
7 changed files with 260 additions and 156 deletions

View File

@ -7,7 +7,7 @@
"jquery": true,
"esnext": true,
"globalstrict": true,
"globals": { "angular":false, "alert":false, "$AnsibleConfig":true, "$basePath":true, "jsyaml":false, "_":true },
"globals": { "angular":false, "alert":false, "$AnsibleConfig":true, "$basePath":true, "jsyaml":false, "_":false, "d3":false, "Donut3D":false },
"strict": false,
"quotmark": false,
"smarttabs": true,

View File

@ -269,7 +269,14 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
'box-shadow': 'none',
'height': 'auto'
});
$('#job-summary-container').css({ "width": "41.66666667%", "padding-right": "15px", "z-index": 0 }).show();
$('#job-summary-container').css({
"width": "41.66666667%",
"padding-left": "7px",
"padding-right": "15px",
"z-index": 0
});
setTimeout(function() { $('#job-summary-container .job_well').height($('#job-detail-container').height() - 18); }, 500);
$('#job-summary-container').show();
}
// Detail table height adjusting. First, put page height back to 'normal'.
$('#plays-table-detail').height(150);

View File

@ -40,9 +40,9 @@
angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('DigestEvents', ['UpdatePlayStatus', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'SelectPlay', 'SelectTask',
'GetHostCount', 'GetElapsed', 'UpdateTaskStatus',
'GetHostCount', 'GetElapsed', 'UpdateTaskStatus', 'DrawGraph',
function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed,
UpdateTaskStatus) {
UpdateTaskStatus, DrawGraph) {
return function(params) {
var scope = params.scope,
@ -156,6 +156,7 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
scope: scope,
id: event.id
});
DrawGraph({ scope: scope });
}
if (event.event === 'runner_on_unreachable') {
@ -229,6 +230,7 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
});
scope.job_status.status = (event.failed) ? 'failed' : 'successful';
scope.job_status.status_class = "";
DrawGraph({ scope: scope });
}
});
};
@ -768,18 +770,66 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
};
}])
.factory('GetHostSummary', [ function() {
.factory('DrawGraph', ['Rest', 'GetBasePath', 'ProcessErrors', function(Rest, GetBasePath, ProcessErrors) {
return function(params) {
var scope = params.scope,
dark = 0, failed = 0, changed = 0, ok = 0,
svg_height, svg_width, graph_data, svg, url;
}])
svg_width = $('#graph-section').width();
svg_height = 300;
if ($('#graph-section svg').length === 0) {
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height);
}
else {
svg = d3.select("#graph-section svg");
}
svg.append("g").attr("id","completedHostsDonutNew");
$('#completedHostsDonutNew').hide();
if (scope.removeRenderGraph) {
scope.removeRenderGraph();
}
scope.removeRenderGraph = scope.$on('RenderGraph', function() {
Donut3D.draw("completedHostsDonutNew", graph_data, Math.floor(svg_width / 2), 150, 130, 100, 15, 0.4);
$('#completedHostsDonut').remove();
$('#completedHostsDonutNew').attr('id','completedHostsDonut');
$('#completedHostsDonut').show();
});
.factory('DrawGraph', [ function() {
/*var salesData=[
{label:"OK", color:"#9ED89E"},
{label:"Changed", color:"#DC3912"},
{label:"Failed", color:"#DA4D49;"},
{label:"Skipped", color:"#D4D4D4"},
{label:"Unreachable", color:""}
];*/
url = GetBasePath('jobs') + scope.job_id + '/job_host_summaries/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
if (data.count) {
data.results.forEach(function(row) {
if (row.dark) {
dark ++;
}
else if (row.failures) {
failed++;
}
else if (row.changed) {
changed++;
}
else if (row.ok) {
ok++;
}
});
graph_data = [
{ label: 'OK', value: ok, color: '#9ED89E' },
{ label: 'Changed', value: changed, color: '#FFC773' },
{ label: 'Failed', value: failed, color: '#DA4D49' },
{ label: 'Unreachable', value: dark, color: '#A9A9A9' }
];
scope.$emit('RenderGraph');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
};
}])
.factory('FilterAllByHostName', ['Rest', 'GetBasePath', 'ProcessErrors', 'SelectPlay', function(Rest, GetBasePath, ProcessErrors, SelectPlay) {
@ -787,7 +837,7 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
var scope = params.scope,
host = params.host,
job_id = scope.job_id,
url = GetBasePath('jobs') + job_id + '/job_events/?event__icontains=runner&host_name__icontains=' + host;
url = GetBasePath('jobs') + job_id + '/job_events/?event__icontains=runner&host_name__icontains=' + host + '&parent__isnull=false';
scope.search_all_tasks = [];
scope.search_all_plays = [];
@ -811,17 +861,22 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
if (row.parent) {
scope.search_all_plays.push(row.parent);
if (data.count > 0) {
data.results.forEach(function(row) {
if (row.parent) {
scope.search_all_plays.push(row.parent);
}
});
if (scope.search_all_plays.length > 0) {
scope.search_all_plays.sort();
scope.activePlay = scope.search_all_plays[scope.search_all_plays.length - 1];
}
else {
scope.activePlay = null;
}
});
if (scope.search_all_plays.length > 0) {
scope.search_all_plays.sort();
scope.activePlay = scope.search_all_plays[scope.search_all_plays.length - 1];
}
else {
scope.activePlay = null;
scope.search_all_plays.push(0);
}
scope.$emit('AllPlaysReady');
})
@ -834,13 +889,18 @@ function(UpdatePlayStatus, UpdateHostStatus, UpdatePlayChild, AddHostResult, Sel
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(row) {
if (row.parent) {
scope.search_all_tasks.push(row.parent);
if (data.count > 0) {
data.results.forEach(function(row) {
if (row.parent) {
scope.search_all_tasks.push(row.parent);
}
});
if (scope.search_all_tasks.length > 0) {
scope.search_all_tasks.sort();
}
});
if (scope.search_all_tasks.length > 0) {
scope.search_all_tasks.sort();
}
else {
scope.search_all_tasks.push(0);
}
scope.$emit('AllTasksReady');
})

View File

@ -142,6 +142,9 @@
padding-left: 15px;
padding-right: 7px;
width: 58.33333333%;
.well {
overflow: hidden;
}
}
#job-summary-container {
@ -303,3 +306,35 @@ label.small-label {
.mCSB_container {
margin-right: 18px;
}
#graph-section {
position: relative;
.legend {
font-size: 12px;
margin-top: 10px;
}
i {
margin-left: 5px;
}
i:first-child {
margin-left: 0;
}
}
path.slice{
stroke-width:2px;
}
polyline{
opacity: .3;
stroke: black;
stroke-width: 2px;
fill: none;
}
svg text.percent{
fill:white;
text-anchor:middle;
font-size:12px;
}

123
awx/ui/static/lib/d3Donut/d3Donut.js vendored Normal file
View File

@ -0,0 +1,123 @@
!function(){
var Donut3D={};
function pieTop(d, rx, ry, ir ){
if(d.endAngle - d.startAngle == 0 ) return "M 0 0";
var sx = rx*Math.cos(d.startAngle),
sy = ry*Math.sin(d.startAngle),
ex = rx*Math.cos(d.endAngle),
ey = ry*Math.sin(d.endAngle);
var ret =[];
ret.push("M",sx,sy,"A",rx,ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0),"1",ex,ey,"L",ir*ex,ir*ey);
ret.push("A",ir*rx,ir*ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0), "0",ir*sx,ir*sy,"z");
return ret.join(" ");
}
function pieOuter(d, rx, ry, h ){
var startAngle = (d.startAngle > Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle > Math.PI ? Math.PI : d.endAngle);
var sx = rx*Math.cos(startAngle),
sy = ry*Math.sin(startAngle),
ex = rx*Math.cos(endAngle),
ey = ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx,h+sy,"A",rx,ry,"0 0 1",ex,h+ey,"L",ex,ey,"A",rx,ry,"0 0 0",sx,sy,"z");
return ret.join(" ");
}
function pieInner(d, rx, ry, h, ir ){
var startAngle = (d.startAngle < Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle < Math.PI ? Math.PI : d.endAngle);
var sx = ir*rx*Math.cos(startAngle),
sy = ir*ry*Math.sin(startAngle),
ex = ir*rx*Math.cos(endAngle),
ey = ir*ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx, sy,"A",ir*rx,ir*ry,"0 0 1",ex,ey, "L",ex,h+ey,"A",ir*rx, ir*ry,"0 0 0",sx,h+sy,"z");
return ret.join(" ");
}
function getPercent(d){
return (d.endAngle-d.startAngle > 0.2 ?
Math.round(1000*(d.endAngle-d.startAngle)/(Math.PI*2))/10+'%' : '');
}
Donut3D.transition = function(id, data, rx, ry, h, ir){
function arcTweenInner(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieInner(i(t), rx+0.5, ry+0.5, h, ir); };
}
function arcTweenTop(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieTop(i(t), rx, ry, ir); };
}
function arcTweenOuter(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieOuter(i(t), rx-.5, ry-.5, h); };
}
function textTweenX(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.cos(0.5*(i(t).startAngle+i(t).endAngle)); };
}
function textTweenY(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.sin(0.5*(i(t).startAngle+i(t).endAngle)); };
}
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
d3.select("#"+id).selectAll(".innerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenInner);
d3.select("#"+id).selectAll(".topSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenTop);
d3.select("#"+id).selectAll(".outerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenOuter);
d3.select("#"+id).selectAll(".percent").data(_data).transition().duration(750)
.attrTween("x",textTweenX).attrTween("y",textTweenY).text(getPercent);
}
Donut3D.draw=function(id, data, x /*center x*/, y/*center y*/,
rx/*radius x*/, ry/*radius y*/, h/*height*/, ir/*inner radius*/){
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
var slices = d3.select("#"+id).append("g").attr("transform", "translate(" + x + "," + y + ")")
.attr("class", "slices");
slices.selectAll(".innerSlice").data(_data).enter().append("path").attr("class", "innerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieInner(d, rx+0.5,ry+0.5, h, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".topSlice").data(_data).enter().append("path").attr("class", "topSlice")
.style("fill", function(d) { return d.data.color; })
.style("stroke", function(d) { return d.data.color; })
.attr("d",function(d){ return pieTop(d, rx, ry, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".outerSlice").data(_data).enter().append("path").attr("class", "outerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieOuter(d, rx-.5,ry-.5, h);})
.each(function(d){this._current=d;});
slices.selectAll(".percent").data(_data).enter().append("text").attr("class", "percent")
.attr("x",function(d){ return 0.6*rx*Math.cos(0.5*(d.startAngle+d.endAngle));})
.attr("y",function(d){ return 0.6*ry*Math.sin(0.5*(d.startAngle+d.endAngle));})
.text(getPercent).each(function(d){this._current=d;});
}
this.Donut3D = Donut3D;
}();

View File

@ -204,7 +204,11 @@
</div><!-- section -->
<div id="graph-section">
<div class="header">
<div class="title">Host Status Overview</div>
<div class="legend"><i class="fa fa-circle successful-hosts-color"></i> Successful <i class="fa fa-circle changed-hosts-color"></i> Changed
<i class="fa fa-circle unreachable-hosts-color"></i> Unreachable <i class="fa fa-circle failed-hosts-color"></i> Failed</div>
</div>
</div><!-- graph section -->
</div>
</div><!-- col-md-5 -->

View File

@ -405,132 +405,7 @@
<script src="{{ STATIC_URL }}lib/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js"></script>
<script scr="{{ STATIC_URL }}lib/lib/jQuery.dotdotdot/src/js/jquery.dotdotdot.min.js"></script>
<script src="{{ STATIC_URL }}lib/d3js/build/d3.v3.min.js"></script>
<script>
!function(){
var Donut3D={};
function pieTop(d, rx, ry, ir ){
if(d.endAngle - d.startAngle == 0 ) return "M 0 0";
var sx = rx*Math.cos(d.startAngle),
sy = ry*Math.sin(d.startAngle),
ex = rx*Math.cos(d.endAngle),
ey = ry*Math.sin(d.endAngle);
var ret =[];
ret.push("M",sx,sy,"A",rx,ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0),"1",ex,ey,"L",ir*ex,ir*ey);
ret.push("A",ir*rx,ir*ry,"0",(d.endAngle-d.startAngle > Math.PI? 1: 0), "0",ir*sx,ir*sy,"z");
return ret.join(" ");
}
function pieOuter(d, rx, ry, h ){
var startAngle = (d.startAngle > Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle > Math.PI ? Math.PI : d.endAngle);
var sx = rx*Math.cos(startAngle),
sy = ry*Math.sin(startAngle),
ex = rx*Math.cos(endAngle),
ey = ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx,h+sy,"A",rx,ry,"0 0 1",ex,h+ey,"L",ex,ey,"A",rx,ry,"0 0 0",sx,sy,"z");
return ret.join(" ");
}
function pieInner(d, rx, ry, h, ir ){
var startAngle = (d.startAngle < Math.PI ? Math.PI : d.startAngle);
var endAngle = (d.endAngle < Math.PI ? Math.PI : d.endAngle);
var sx = ir*rx*Math.cos(startAngle),
sy = ir*ry*Math.sin(startAngle),
ex = ir*rx*Math.cos(endAngle),
ey = ir*ry*Math.sin(endAngle);
var ret =[];
ret.push("M",sx, sy,"A",ir*rx,ir*ry,"0 0 1",ex,ey, "L",ex,h+ey,"A",ir*rx, ir*ry,"0 0 0",sx,h+sy,"z");
return ret.join(" ");
}
function getPercent(d){
return (d.endAngle-d.startAngle > 0.2 ?
Math.round(1000*(d.endAngle-d.startAngle)/(Math.PI*2))/10+'%' : '');
}
Donut3D.transition = function(id, data, rx, ry, h, ir){
function arcTweenInner(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieInner(i(t), rx+0.5, ry+0.5, h, ir); };
}
function arcTweenTop(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieTop(i(t), rx, ry, ir); };
}
function arcTweenOuter(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return pieOuter(i(t), rx-.5, ry-.5, h); };
}
function textTweenX(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.cos(0.5*(i(t).startAngle+i(t).endAngle)); };
}
function textTweenY(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) { return 0.6*rx*Math.sin(0.5*(i(t).startAngle+i(t).endAngle)); };
}
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
d3.select("#"+id).selectAll(".innerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenInner);
d3.select("#"+id).selectAll(".topSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenTop);
d3.select("#"+id).selectAll(".outerSlice").data(_data)
.transition().duration(750).attrTween("d", arcTweenOuter);
d3.select("#"+id).selectAll(".percent").data(_data).transition().duration(750)
.attrTween("x",textTweenX).attrTween("y",textTweenY).text(getPercent);
}
Donut3D.draw=function(id, data, x /*center x*/, y/*center y*/,
rx/*radius x*/, ry/*radius y*/, h/*height*/, ir/*inner radius*/){
var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);
var slices = d3.select("#"+id).append("g").attr("transform", "translate(" + x + "," + y + ")")
.attr("class", "slices");
slices.selectAll(".innerSlice").data(_data).enter().append("path").attr("class", "innerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieInner(d, rx+0.5,ry+0.5, h, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".topSlice").data(_data).enter().append("path").attr("class", "topSlice")
.style("fill", function(d) { return d.data.color; })
.style("stroke", function(d) { return d.data.color; })
.attr("d",function(d){ return pieTop(d, rx, ry, ir);})
.each(function(d){this._current=d;});
slices.selectAll(".outerSlice").data(_data).enter().append("path").attr("class", "outerSlice")
.style("fill", function(d) { return d3.hsl(d.data.color).darker(0.7); })
.attr("d",function(d){ return pieOuter(d, rx-.5,ry-.5, h);})
.each(function(d){this._current=d;});
slices.selectAll(".percent").data(_data).enter().append("text").attr("class", "percent")
.attr("x",function(d){ return 0.6*rx*Math.cos(0.5*(d.startAngle+d.endAngle));})
.attr("y",function(d){ return 0.6*ry*Math.sin(0.5*(d.startAngle+d.endAngle));})
.text(getPercent).each(function(d){this._current=d;});
}
this.Donut3D = Donut3D;
}();
</script>
<script src="{{ STATIC_URL }}lib/d3Donut/d3Donut.js"></script>
<script>
// When user clicks on main tab, fire the matching Angular route