Fix assorted Job Details bugs

* fix strict equality check that broke host summary event signals, fix hosts value provided to host-event modal, add all jobDetail.* routes to event emitter used by stdout, resolves #1733

* fix over-broad clear on intervals driving job details / stdout updates #1799

* fixes assorted job details audit issues

* feedback on PR #1799
This commit is contained in:
Leigh 2016-05-06 12:23:08 -04:00
parent ea25742ea5
commit 1af0a172d3
14 changed files with 85 additions and 67 deletions

View File

@ -670,15 +670,15 @@ var tower = angular.module('Tower', [
// accessing.
if ($state.is('jobs')) {
$rootScope.$emit('JobStatusChange-jobs', data);
} else if ($state.is('jobDetail') ||
} else if ($state.includes('jobDetail') ||
$state.is('adHocJobStdout') ||
$state.is('inventorySyncStdout') ||
$state.is('managementJobStdout') ||
$state.is('scmUpdateStdout')) {
$state.is('scmUpdateStdout')){
$log.debug("sending status to standard out");
$rootScope.$emit('JobStatusChange-jobStdout', data);
} if ($state.is('jobDetail')) {
} if ($state.includes('jobDetail')) {
$rootScope.$emit('JobStatusChange-jobDetails', data);
} else if ($state.is('dashboard')) {
$rootScope.$emit('JobStatusChange-home', data);
@ -739,10 +739,19 @@ var tower = angular.module('Tower', [
}
// remove any lingering intervals
if ($rootScope.jobDetailInterval) {
// except on jobDetails.* states
var jobDetailStates = [
'jobDetail',
'jobDetail.host-summary',
'jobDetail.host-event.details',
'jobDetail.host-event.json',
'jobDetail.host-events',
'jobDetail.host-event.stdout'
];
if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobDetailInterval);
}
if ($rootScope.jobStdOutInterval) {
if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobStdOutInterval);
}

View File

@ -6,8 +6,8 @@
export default
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'event', 'CodeMirror',
function($stateParams, $scope, $state, Wait, JobDetailService, event, CodeMirror){
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent',
function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent){
$scope.processEventStatus = JobDetailService.processEventStatus;
$scope.hostResults = [];
@ -56,7 +56,7 @@
};
var init = function(){
$scope.event = event;
$scope.event = hostEvent;
JobDetailService.getJobEventChildren($stateParams.taskId).success(function(res){
$scope.hostResults = res.results;
});

View File

@ -12,10 +12,10 @@ var hostEventModal = {
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
resolve: {
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
hostEvent: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId
}).success(function(res){ return res.results[0];});
}).then(function(res){ return res.data.results[0];});
}]
},
onExit: function(){

View File

@ -72,8 +72,7 @@
if (filter === 'ok'){
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName,
or__field__event: 'runner_on_ok',
changed: false
event__startswith: 'runner_on_ok'
})
.success(function(res){
$scope.results = res.results;
@ -93,7 +92,9 @@
if (filter === 'failed'){
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName,
failed: true})
failed: true,
event__startswith: 'runner_on_failed'
})
.success(function(res){
$scope.results = res.results;
Wait('stop');

View File

@ -5,7 +5,7 @@
text-anchor: end !important;
}
.HostSummary-graph--changed{
text-anchor: end !important;
text-anchor: start !important;
}
.HostSUmmary-graph--unreachable{}
.HostSummary-loading{

View File

@ -53,13 +53,13 @@
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context
if ($stateParams.id === data.unified_job_id){
if (parseInt($stateParams.id) === data.unified_job_id){
init();
}
});
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) {
if ($stateParams.id === data.unified_job_id){
if (parseInt($stateParams.id) === data.unified_job_id){
$scope.status = data.status;
}
});
@ -78,11 +78,6 @@
return n !== 0 ? n + dict[n] + status : dict[n] + status;
}
};
/*
_.forIn(count, function(value, key){
text[key] = grammar(value.length, key);
});
*/
return grammar(n, status);
};
$scope.getNextPage = function(){

View File

@ -1,5 +1,6 @@
<div id="hosts-summary-section" class="section">
<div class="JobDetail-instructions"><span class="badge">4</span> Please select a host below to view a summary of all associated tasks.</div>
<div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
@ -43,17 +44,17 @@
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ buildTooltip(host.ok - host.changed, 'ok') }}" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok - host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'changed'})" aw-tool-tip="{{buildTooltip(host.changed, 'changed')}}" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'skipped'})" aw-tool-tip="{{buildTooltip(host.skipped, 'skipped')}}" data-placement="top" ng-hide="host.skipped == 0"><span class="badge skipped-hosts">{{ host.skipped }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{buildTooltip(host.dark, 'unreachable')}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, filter: 'failed'})" aw-tool-tip="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{buildTooltip(host.dark, 'unreachable')}}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'failed'})" aw-tool-tip="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
</td>
</tr>
<tr ng-show="hosts.length === 0 && status === 'pending'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Waiting...</td>
<tr ng-show="status == 'pending'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Initiating job run.</td>
</tr>
<tr ng-show="hosts.length === 0 && status === 'running' ">
<td colspan="5" class="col-lg-12 HostSummary-loading">Loading...</td>
<tr ng-show="status == 'running'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Job is running. Summary will be available on completion.</td>
</tr>
<tr ng-show="status === 'failed' || status === 'successful' && hosts.length === 0 ">
<tr ng-show="(status === 'failed' || status === 'successful') && hosts.length === 0 ">
<td colspan="2" class="col-lg-12 HostSummary-loading">No matching hosts</td>
</tr>
</tbody>
@ -65,7 +66,7 @@
</div>
</div><!-- section -->
<div class="JobDetail-panelHeaderText">Host Status Summary</div>
<div id="graph-section" class="JobDetail-graphSection">
<svg></svg>
</div>

View File

@ -8,16 +8,21 @@ import {templateUrl} from '../../shared/template-url/template-url.factory';
export default {
name: 'jobDetail.host-summary',
url: '/event-summary',
/*
resolve: {
jobSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
var job_socket = Socket({
scope: $rootScope,
endpoint: "jobs"
});
job_socket.init();
return job_socket;
var job_socket = Socket({
scope: $rootScope,
endpoint: "jobs"
});
if (!$rootScope.event_socket){
job_socket.init();
}
return job_socket;
}]
},
*/
views:{
'host-summary': {
controller: 'HostSummaryController',

View File

@ -7,6 +7,13 @@
@breakpoint-md: 1200px;
@breakpoint-sm: 420px;
.JobDetail-tasks.section{
margin-top:40px;
}
.JobDetail-instructions{
color: @default-interface-txt;
margin: 10px 0 10px 0;
}
.JobDetail{
.OnePlusOne-container(100%, @breakpoint-md);
}
@ -115,7 +122,7 @@
flex-wrap: wrap;
flex-direction: row;
height: 50px;
margin-top: 25px;
margin-top: 20px;
@media screen and(max-width: @breakpoint-sm){
height: auto;
}

View File

@ -13,7 +13,7 @@
export default
[ '$location', '$rootScope', '$filter', '$scope', '$compile',
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
'LoadPlays', 'LoadTasks', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
@ -21,7 +21,7 @@ export default
function(
$location, $rootScope, $filter, $scope, $compile, $stateParams,
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
SelectPlay, SelectTask, Socket, GetElapsed,
SelectPlay, SelectTask, GetElapsed,
JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
PlaybookRun, LoadPlays, LoadTasks,
@ -680,7 +680,7 @@ export default
// there's a bunch of white space at the bottom, let's use it
$('#plays-table-detail').height(80 + (height * 0.10));
$('#tasks-table-detail').height(120 + (height * 0.20));
$('#hosts-table-detail').height(150 + (height * 0.70));
$('#hosts-table-detail').height(150 + (height * 0.10));
}
scope.$emit('RefreshCompleted');
};
@ -737,7 +737,7 @@ export default
return true;
};
scope.toggleLessEvents = function() {
scope.toggleLessEvents = function(state) {
if (!scope.lessEvents) {
$('#events-summary').slideUp(200);
scope.lessEvents = true;

View File

@ -171,6 +171,7 @@
<div id="job-detail-details">
<div id="play-section">
<div class="JobDetail-instructions"><span class="badge">1</span> Please select from a play below to view its associated tasks.</div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
@ -228,7 +229,8 @@
</div>
<!-- end of plays section of details-->
<div id="task-section" class="section" >
<div id="task-section" class="section JobDetail-tasks" >
<div class="JobDetail-instructions"><span class="badge">2</span> Please select a task below to view its associated hosts</div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
@ -312,6 +314,7 @@
<!--end of tasks section of details-->
<div id="task-hosts-section" class="section">
<div class="JobDetail-instructions"><span class="badge">3</span> Please select a host below to view associated task details.</div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
@ -368,19 +371,12 @@
</div>
<!--end of details-->
</div>
<!--end of job-detail-container (left side)-->
<!--beginning of stdout-->
<div class="JobDetail-rightSide">
<!--beginning of events summary-->
<div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen && job.summary_fields.unified_job_template.unified_job_type == 'job'">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-left"></i>
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a>
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-down"></i>
@ -396,6 +392,11 @@
<!-- end of events summary-->
</div>
<!--end of job-detail-container (left side)-->
<!--beginning of stdout-->
<div class="JobDetail-rightSide">
<div class="JobDetail-stdoutPanel Panel">
<div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
@ -403,7 +404,7 @@
<button class="StandardOut-actionButton" aw-tool-tip="Toggle Output" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i>
</button>
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<a ng-show="job_status.status === ('failed' || 'successful')" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
<i class="fa fa-download"></i>
</button>

View File

@ -35,10 +35,6 @@ angular.module('ApiLoader', ['Utilities'])
.success(function (data) {
data.base = base;
$rootScope.defaultUrls = data;
// tiny hack to side-step api/v1/job_events not being a visible endpoint @ GET api/v1/
if (!$rootScope.defaultUrls.job_events){
$rootScope.defaultUrls.job_events = '/api/v1/job_events/';
}
Store('api', data);
})
.error(function (data, status) {

View File

@ -22,14 +22,14 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
// Open up a socket for events depending on the type of job
function openSockets() {
if ($state.current.name === 'jobDetail') {
$log.debug("socket watching on job_events-" + job_id);
$rootScope.event_socket.on("job_events-" + job_id, function() {
$log.debug("socket fired on job_events-" + job_id);
if (api_complete) {
event_queue++;
}
});
}
$log.debug("socket watching on job_events-" + job_id);
$rootScope.event_socket.on("job_events-" + job_id, function() {
$log.debug("socket fired on job_events-" + job_id);
if (api_complete) {
event_queue++;
}
});
}
if ($state.current.name === 'adHocJobStdout') {
$log.debug("socket watching on ad_hoc_command_events-" + job_id);
$rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() {
@ -131,8 +131,6 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
start: (data.range.start < 0) ? 0 : data.range.start,
end: data.range.end
});
//console.log('loaded start: ' + data.range.start + ' end: ' + data.range.end);
//console.log(data.content);
if ($scope.should_apply_live_events) {
// if user has not disabled live event view by scrolling upward, then scroll down to the new content
current_range = data.range;

View File

@ -3,6 +3,11 @@
/** @define StandardOut */
.StandardOut-preContent{
font-size: 12px;
padding: 0 20px 0 20px;
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
}
.StandardOut-container {
.OnePlusTwo-container;
}
@ -24,8 +29,8 @@
min-height: 200px;
background-color: @default-secondary-bg;
border-radius: 5px;
height: ~"calc(100% - 74px)";
overflow: scroll;
max-height: 1600px;
overflow: auto;
}
.StandardOut-details {
@ -81,7 +86,7 @@
font-size: 16px;
height: 30px;
min-width: 30px;
color: @default-data-txt;
color: @list-action-icon;
background-color: inherit;
border: none;
border-radius: 50%;