From fcb9d8b6b58acfe1f52dc456a54a579a20a73a72 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Thu, 29 Sep 2016 11:15:13 -0400 Subject: [PATCH] Adding job results bar files --- .../client/src/job-results/duration.filter.js | 120 ++++++++++++++++++ .../host-status-bar.block.less | 39 ++++++ .../host-status-bar.directive.js | 22 ++++ .../host-status-bar.partial.html | 7 + .../src/job-results/host-status-bar/main.js | 11 ++ .../src/job-results/job-results.controller.js | 28 +++- .../src/job-results/job-results.partial.html | 6 +- .../src/job-results/job-results.route.js | 14 ++ .../src/job-results/job-results.service.js | 64 +++++++++- awx/ui/client/src/job-results/main.js | 7 +- 10 files changed, 305 insertions(+), 13 deletions(-) create mode 100644 awx/ui/client/src/job-results/duration.filter.js create mode 100644 awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less create mode 100644 awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js create mode 100644 awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html create mode 100644 awx/ui/client/src/job-results/host-status-bar/main.js diff --git a/awx/ui/client/src/job-results/duration.filter.js b/awx/ui/client/src/job-results/duration.filter.js new file mode 100644 index 0000000000..ba23374784 --- /dev/null +++ b/awx/ui/client/src/job-results/duration.filter.js @@ -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) + ); + } + }; + }; diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less new file mode 100644 index 0000000000..9e85b992a6 --- /dev/null +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less @@ -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; +} diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js new file mode 100644 index 0000000000..e1268336a4 --- /dev/null +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js @@ -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 + + } + } +}]; diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html new file mode 100644 index 0000000000..bc27f817fb --- /dev/null +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html @@ -0,0 +1,7 @@ +
+
+
+
+
+
+
diff --git a/awx/ui/client/src/job-results/host-status-bar/main.js b/awx/ui/client/src/job-results/host-status-bar/main.js new file mode 100644 index 0000000000..2b17a2e414 --- /dev/null +++ b/awx/ui/client/src/job-results/host-status-bar/main.js @@ -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); diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 843d5936a6..65b7d86080 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -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; + } + }); + + }]; diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index 038998ef14..3300afa695 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -447,7 +447,7 @@
+ fa icon-job-{{ job.status }}"> {{ job.name }}
@@ -483,7 +483,7 @@ Elapsed
- {{ job_status.elapsed || "00:00:01"}} + {{ job.elapsed * 1000 | duration: "hh:mm:ss" }} @@ -513,6 +513,8 @@ + + diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js index eb6d72b1a7..5268acd0fa 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -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'), diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js index fd4a10f588..57016653c4 100644 --- a/awx/ui/client/src/job-results/job-results.service.js +++ b/awx/ui/client/src/job-results/job-results.service.js @@ -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; }]; diff --git a/awx/ui/client/src/job-results/main.js b/awx/ui/client/src/job-results/main.js index 6a67cd000f..104ca76086 100644 --- a/awx/ui/client/src/job-results/main.js +++ b/awx/ui/client/src/job-results/main.js @@ -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);