Adding job results bar files

This commit is contained in:
jaredevantabor 2016-09-29 11:15:13 -04:00
parent 29c8ef72e0
commit fcb9d8b6b5
10 changed files with 305 additions and 13 deletions

View File

@ -0,0 +1,120 @@
// TODO: come back and get this library
export default function() {
var DURATION_FORMATS_SPLIT = /((?:[^ydhms']+)|(?:'(?:[^']|'')*')|(?:y+|d+|h+|m+|s+))(.*)/;
var DURATION_FORMATS = {
'y': { // years
// "longer" years are not supported
value: 365 * 24 * 60 * 60 * 1000
},
'yy': {
value: 'y',
pad: 2
},
'd': { // days
value: 24 * 60 * 60 * 1000
},
'dd': {
value: 'd',
pad: 2
},
'h': { // hours
value: 60 * 60 * 1000
},
'hh': { // padded hours
value: 'h',
pad: 2
},
'm': { // minutes
value: 60 * 1000
},
'mm': { // padded minutes
value: 'm',
pad: 2
},
's': { // seconds
value: 1000
},
'ss': { // padded seconds
value: 's',
pad: 2
},
'sss': { // milliseconds
value: 1
},
'ssss': { // padded milliseconds
value: 'sss',
pad: 4
}
};
function _parseFormat(string) {
// @inspiration AngularJS date filter
var parts = [];
var format = string;
while(format) {
var match = DURATION_FORMATS_SPLIT.exec(format);
if (match) {
parts = parts.concat(match.slice(1));
format = parts.pop();
} else {
parts.push(format);
format = null;
}
}
return parts;
}
function _formatDuration(timestamp, format) {
var text = '';
var values = { };
format.filter(function(format) { // filter only value parts of format
return DURATION_FORMATS.hasOwnProperty(format);
}).map(function(format) { // get formats with values only
var config = DURATION_FORMATS[format];
if(config.hasOwnProperty('pad')) {
return config.value;
} else {
return format;
}
}).filter(function(format, index, arr) { // remove duplicates
return (arr.indexOf(format) === index);
}).map(function(format) { // get format configurations with values
return angular.extend({
name: format,
}, DURATION_FORMATS[format]);
}).sort(function(a, b) { // sort formats descending by value
return b.value - a.value;
}).forEach(function(format) { // create values for format parts
var value = values[format.name] = Math.floor(timestamp / format.value);
timestamp = timestamp - (value * format.value);
});
format.forEach(function(part) {
var format = DURATION_FORMATS[part];
if(format) {
var value = values[format.value];
text += (format.hasOwnProperty('pad') ? _padNumber(value, Math.max(format.pad, value.toString().length)) : values[part]);
} else {
text += part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
}
});
return text;
}
function _padNumber(number, len) {
return ((new Array(len + 1)).join('0') + number).slice(-len);
}
return function(value, format) {
if (typeof value !== "number") {
return value;
}
var timestamp = parseInt(value.valueOf(), 10);
if(isNaN(timestamp)) {
return value;
} else {
return _formatDuration(
timestamp,
_parseFormat(format)
);
}
};
};

View File

@ -0,0 +1,39 @@
@import '../../shared/branding/colors.default.less';
.JobResults-hostStatusBar{
display: flex;
flex: 1 0 auto;
width: 100%;
margin-top: 10px;
}
.JobResults-hostStatusBar--ok{
background-color: @default-succ;
display: flex;
flex: 1 0 auto;
height: 5px;
}
.JobResults-hostStatusBar--changed{
background-color: @default-warning;
flex: 1 0 auto;
height: 5px;
}
.JobResults-hostStatusBar--unreachable{
background-color: @default-unreachable;
flex: 1 0 auto;
height: 5px;
}
.JobResults-hostStatusBar--failures{
background-color: @default-err;
flex: 1 0 auto;
height: 5px;
}
.JobResults-hostStatusBar--skipped{
background-color: @default-link;
flex: 1 0 auto;
height: 5px;
}

View File

@ -0,0 +1,22 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
// import hostStatusBarController from './host-status-bar.controller';
export default [ 'templateUrl',
function(templateUrl) {
return {
scope: {
jobData: '='
},
templateUrl: templateUrl('job-results/host-status-bar/host-status-bar'),
restrict: 'E',
// controller: standardOutLogController,
link: function(scope) {
// All of our DOM related stuff will go in here
}
}
}];

View File

@ -0,0 +1,7 @@
<div class="JobResults-hostStatusBar">
<div class="JobResults-hostStatusBar--ok"></div>
<div class="JobResults-hostStatusBar--changed"></div>
<div class="JobResults-hostStatusBar--failures"></div>
<div class="JobResults-hostStatusBar--unreachable"></div>
<div class="JobResults-hostStatusBar--skipped"></div>
</div>

View File

@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import hostStatusBar from './host-status-bar.directive';
export default
angular.module('hostStatusBarDirective', [])
.directive('hostStatusBar', hostStatusBar);

View File

@ -1,4 +1,4 @@
export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', function(jobData, jobDataOptions, jobLabels, $scope, ParseTypeChange, ParseVariableString, jobResultsService) {
export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', '$rootScope', function(jobData, jobDataOptions, jobLabels, $scope, ParseTypeChange, ParseVariableString, jobResultsService, $rootScope) {
var getTowerLinks = function() {
var getTowerLink = function(key) {
if ($scope.job.related[key]) {
@ -57,7 +57,7 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
$scope.toggleStdoutFullscreen = function() {
$scope.stdoutFullScreen = !$scope.stdoutFullScreen;
};
$scope.deleteJob = function() {
jobResultsService.deleteJob($scope.job);
};
@ -66,9 +66,23 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
jobResultsService.cancelJob($scope.job);
};
// Set the job status
// TODO: pull from websockets
$scope.job_status = {"status": ""};
$scope.job_status.status = (jobData.status === 'waiting' ||
jobData.status === 'new') ? 'pending' : jobData.status;
$rootScope.event_socket.on("job_events-" + $scope.job.id, function(data) {
console.log(data);
if(data.event_name === "playbook_on_stats"){
// get the data for populating the host status bar
jobResultsService.getHostStatusBarCounts(data.event_data);
}
});
$rootScope.$on('JobStatusChange-jobDetails', function(e, data) {
console.log(data);
if (parseInt(data.unified_job_id, 10) === parseInt($scope.job.id,10)) {
$scope.job.status = data.status;
// $scope.job.elapsed = data.elapsed;
}
});
}];

View File

@ -447,7 +447,7 @@
<div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">
<i class="JobResults-statusResultIcon
fa icon-job-{{ job_status.status }}">
fa icon-job-{{ job.status }}">
</i>
{{ job.name }}
</div>
@ -483,7 +483,7 @@
Elapsed
</div>
<span class="badge List-titleBadge">
{{ job_status.elapsed || "00:00:01"}}
{{ job.elapsed * 1000 | duration: "hh:mm:ss" }}
</span>
</div>
@ -513,6 +513,8 @@
</div>
</div>
<host-status-bar></host-status-bar>
<!-- <standard-out-log stdout-endpoint="job.related.stdout"></standard-out-log> -->
</div>
</div>
</div>

View File

@ -74,6 +74,20 @@ export default {
val.reject(data);
});
return val.promise;
}],
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
if (!$rootScope.event_socket) {
$rootScope.event_socket = Socket({
scope: $rootScope,
endpoint: "job_events"
});
$rootScope.event_socket.init();
// returns should really be providing $rootScope.event_socket
// otherwise, we have to inject the entire $rootScope into the controller
return true;
} else {
return true;
}
}]
},
templateUrl: templateUrl('job-results/job-results'),

View File

@ -5,8 +5,67 @@
*************************************************/
export default ['Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', function (Prompt, $filter, Wait, Rest, $state, ProcessErrors) {
return {
export default ['Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', '$rootScope', function (Prompt, $filter, Wait, Rest, $state, ProcessErrors, $rootScope) {
var val = {
getHostStatusBarCounts: function(event_data) {
var hosts = {};
// iterate over the event_data and populate an object with hosts
// and their status data
Object.keys(event_data).forEach(key => {
// failed passes boolean not integer
if (key === "failed") {
// array of hosts from failed type
var hostsArr = Object.keys(event_data[key]);
hostsArr.forEach(host => {
if (!hosts[host]) {
// host has not been added to hosts object
// add now
hosts[host] = {};
}
hosts[host][key] = event_data[key][host];
});
} else {
// array of hosts from each type ("changed", "dark", etc.)
var hostsArr = Object.keys(event_data[key]);
hostsArr.forEach(host => {
if (!hosts[host]) {
// host has not been added to hosts object
// add now
hosts[host] = {};
}
if (!hosts[host][key]) {
// host doesn't have key
hosts[host][key] = 0;
}
hosts[host][key] += event_data[key][host];
});
}
});
// use the hosts data populate above to get the count
var count = {
ok : _.filter(hosts, function(o){
return !o.failures && !o.changed && o.ok > 0;
}),
skipped : _.filter(hosts, function(o){
return o.skipped > 0;
}),
unreachable : _.filter(hosts, function(o){
return o.dark > 0;
}),
failures : _.filter(hosts, function(o){
return o.failed === true;
}),
changed : _.filter(hosts, function(o){
return o.changed > 0;
})
};
return count;
},
deleteJob: function(job) {
Prompt({
hdr: 'Delete Job',
@ -100,4 +159,5 @@ export default ['Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors',
});
}
};
return val;
}];

View File

@ -6,10 +6,13 @@
import route from './job-results.route.js';
import jobResultsService from './job-results.service';
import hostStatusBarDirective from './host-status-bar/main';
import durationFilter from './duration.filter';
export default
angular.module('jobResults', [])
angular.module('jobResults', [hostStatusBarDirective.name])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}])
.service('jobResultsService', jobResultsService);
.service('jobResultsService', jobResultsService)
.filter('duration', durationFilter);