diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index 9dce01c863..c68c6c5826 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -36,7 +36,10 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $ $scope.removeChoicesReady = $scope.$on('choicesReady', function() { $scope[list.name].forEach(function(item, item_idx) { var itm = $scope[list.name][item_idx]; - + if(item.summary_fields && item.summary_fields.source_workflow_job && + item.summary_fields.source_workflow_job.id){ + item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`; + } // Set the item type label if (list.fields.type) { $scope.type_choices.every(function(choice) { 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 1c98dd3895..b2f83ede02 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -64,17 +64,25 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count' // turn related api browser routes into tower routes getTowerLinks(); + + // the links below can't be set in getTowerLinks because the + // links on the UI don't directly match the corresponding URL + // on the API browser if(jobData.summary_fields && jobData.summary_fields.job_template && jobData.summary_fields.job_template.id){ $scope.job_template_link = `/#/templates/job_template/${$scope.job.summary_fields.job_template.id}`; } if(jobData.summary_fields && jobData.summary_fields.project_update && jobData.summary_fields.project_update.status){ - $scope.project_status = jobData.summary_fields.project_update.status; + $scope.project_status = jobData.summary_fields.project_update.status; } if(jobData.summary_fields && jobData.summary_fields.project_update && jobData.summary_fields.project_update.id){ - $scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`; + $scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`; + } + if(jobData.summary_fields && jobData.summary_fields.source_workflow_job && + jobData.summary_fields.source_workflow_job.id){ + $scope.workflow_result_link = `/#/workflows/${jobData.summary_fields.source_workflow_job.id}`; } // use options labels to manipulate display of details diff --git a/awx/ui/client/src/lists/AllJobs.js b/awx/ui/client/src/lists/AllJobs.js index e5a47371c4..152bf1185d 100644 --- a/awx/ui/client/src/lists/AllJobs.js +++ b/awx/ui/client/src/lists/AllJobs.js @@ -43,7 +43,7 @@ export default ngClick: "viewJobDetails(job)", badgePlacement: 'right', badgeCustom: true, - badgeIcon: ` diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index d38a6e8e66..b636cc1db8 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -93,12 +93,6 @@ export default // ex: 'ws-jobs-' str = `ws-${data.group_name}-${data.job}`; } - else if(data.group_name==="workflow_events"){ - // The naming scheme is "ws" then a - // dash (-) and the group_name, then the job ID - // ex: 'ws-jobs-' - str = `ws-${data.group_name}-${data.workflow_job_id}`; - } else if(data.group_name==="ad_hoc_command_events"){ // The naming scheme is "ws" then a // dash (-) and the group_name, then the job ID diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index ffed821d7d..2edfa68abd 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -58,6 +58,10 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; $scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; $scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : ''; + if(data.summary_fields && data.summary_fields.source_workflow_job && + data.summary_fields.source_workflow_job.id){ + $scope.workflow_result_link = `/#/workflows/${data.summary_fields.source_workflow_job.id}`; + } $scope.playbook = data.playbook; $scope.credential = data.credential; $scope.cloud_credential = data.cloud_credential; diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js index d80385141c..af94f001c3 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ b/awx/ui/client/src/workflow-results/workflow-results.controller.js @@ -7,6 +7,8 @@ export default ['workflowData', 'ParseTypeChange', 'ParseVariableString', 'WorkflowService', + 'count', + '$state', function(workflowData, workflowResultsService, workflowDataOptions, @@ -15,7 +17,9 @@ export default ['workflowData', $scope, ParseTypeChange, ParseVariableString, - WorkflowService + WorkflowService, + count, + $state ) { var getTowerLinks = function() { @@ -57,6 +61,7 @@ export default ['workflowData', $scope.workflow_nodes = workflowNodes; $scope.workflowOptions = workflowDataOptions.actions.GET; $scope.labels = jobLabels; + $scope.count = count.val; // turn related api browser routes into tower routes getTowerLinks(); @@ -113,22 +118,38 @@ export default ['workflowData', init(); - $scope.$on(`ws-workflow_events-${$scope.workflow.id}`, function(e, data) { - - WorkflowService.updateStatusOfNode({ - treeData: $scope.treeData, - nodeId: data.workflow_node_id, - status: data.status, - unified_job_id: data.unified_job_id - }); - - $scope.$broadcast("refreshWorkflowChart"); - }); - // Processing of job-status messages from the websocket $scope.$on(`ws-jobs`, function(e, data) { + // Update the workflow job's unified job: if (parseInt(data.unified_job_id, 10) === parseInt($scope.workflow.id,10)) { - $scope.workflow.status = data.status; + $scope.workflow.status = data.status; + + if(data.status === "successful" || data.status === "failed"){ + $state.go('.', null, { reload: true }); + } + } + // Update the jobs spawned by the workflow: + if(data.hasOwnProperty('workflow_job_id') && + parseInt(data.workflow_job_id, 10) === parseInt($scope.workflow.id,10)){ + + WorkflowService.updateStatusOfNode({ + treeData: $scope.treeData, + nodeId: data.workflow_node_id, + status: data.status, + unified_job_id: data.unified_job_id + }); + + $scope.workflow_nodes.forEach(node => { + if(parseInt(node.id) === parseInt(data.workflow_node_id)){ + node.summary_fields.job = { + status: data.status + }; + } + }); + + $scope.count = workflowResultsService + .getCounts($scope.workflow_nodes); + $scope.$broadcast("refreshWorkflowChart"); } }); }]; diff --git a/awx/ui/client/src/workflow-results/workflow-results.route.js b/awx/ui/client/src/workflow-results/workflow-results.route.js index 70b54c940d..904584ec25 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.route.js +++ b/awx/ui/client/src/workflow-results/workflow-results.route.js @@ -18,8 +18,7 @@ export default { data: { socket: { "groups":{ - "jobs": ["status_changed"], - "workflow_events": [] + "jobs": ["status_changed"] } } }, @@ -61,6 +60,18 @@ export default { }); return defer.promise; }], + // after the GET for the workflow & it's nodes, this helps us keep the + // status bar from flashing as rest data comes in. If the workflow + // is finished and there's a playbook_on_stats event, go ahead and + // resolve the count so you don't get that flashing! + count: ['workflowData', 'workflowNodes', 'workflowResultsService', 'Rest', '$q', function(workflowData, workflowNodes, workflowResultsService, Rest, $q) { + var defer = $q.defer(); + defer.resolve({ + val: workflowResultsService + .getCounts(workflowNodes), + countFinished: true}); + return defer.promise; + }], // GET for the particular jobs labels to be displayed in the // left-hand pane jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { diff --git a/awx/ui/client/src/workflow-results/workflow-results.service.js b/awx/ui/client/src/workflow-results/workflow-results.service.js index 08d38378d8..2d3fddf2f4 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.service.js +++ b/awx/ui/client/src/workflow-results/workflow-results.service.js @@ -7,6 +7,31 @@ export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun) { var val = { + getCounts: function(workflowNodes){ + var nodeArr = []; + workflowNodes.forEach(node => { + if(node && node.summary_fields && node.summary_fields.job && node.summary_fields.job.status){ + nodeArr.push(node.summary_fields.job.status); + } + }); + // use the workflow nodes data populate above to get the count + var count = { + successful : _.filter(nodeArr, function(o){ + return o === "successful"; + }), + failed : _.filter(nodeArr, function(o){ + return o === "failed" || o === "error" || o === "canceled"; + }) + }; + + // turn the count into an actual count, rather than a list of + // statuses + Object.keys(count).forEach(key => { + count[key] = count[key].length; + }); + + return count; + }, deleteJob: function(workflow) { Prompt({ hdr: 'Delete Job', diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less index 38e57d4883..3f9bf3d8f0 100644 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less +++ b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less @@ -5,42 +5,31 @@ flex: 0 0 auto; width: 100%; margin-top: 10px; + margin-bottom: 15px; } -.WorkflowStatusBar-ok, -.WorkflowStatusBar-changed, -.WorkflowStatusBar-unreachable, -.WorkflowStatusBar-failures, -.WorkflowStatusBar-skipped, +.WorkflowStatusBar-successful, +.WorkflowStatusBar-failed, +.WorkflowStatusBar-pending, .WorkflowStatusBar-noData { height: 15px; border-top: 5px solid @default-bg; border-bottom: 5px solid @default-bg; } -.WorkflowStatusBar-ok { +.WorkflowStatusBar-successful { background-color: @default-succ; display: flex; flex: 0 0 auto; } -.WorkflowStatusBar-changed { - background-color: @default-warning; - flex: 0 0 auto; -} - -.WorkflowStatusBar-unreachable { - background-color: @default-unreachable; - flex: 0 0 auto; -} - -.WorkflowStatusBar-failures { +.WorkflowStatusBar-failed { background-color: @default-err; flex: 0 0 auto; } -.WorkflowStatusBar-skipped { - background-color: @default-link; +.WorkflowStatusBar-pending { + background-color: @b7grey; flex: 0 0 auto; } @@ -58,23 +47,14 @@ border-radius: 5px; } -.WorkflowStatusBar-tooltipBadge--ok { +.WorkflowStatusBar-tooltipBadge--successful { background-color: @default-succ; } -.WorkflowStatusBar-tooltipBadge--unreachable { - background-color: @default-unreachable; -} - -.WorkflowStatusBar-tooltipBadge--skipped { - background-color: @default-link; -} - -.WorkflowStatusBar-tooltipBadge--changed { - background-color: @default-warning; -} - -.WorkflowStatusBar-tooltipBadge--failures { +.WorkflowStatusBar-tooltipBadge--failed { background-color: @default-err; - +} + +.WorkflowStatusBar-tooltipBadge--pending { + background-color: @b7grey; } diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js index a6899eb0da..c53fc2dcba 100644 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js +++ b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js @@ -4,27 +4,25 @@ * All Rights Reserved *************************************************/ -// import WorkflowStatusBarController from './host-status-bar.controller'; export default [ 'templateUrl', function(templateUrl) { return { scope: true, templateUrl: templateUrl('workflow-results/workflow-status-bar/workflow-status-bar'), restrict: 'E', - // controller: standardOutLogController, link: function(scope) { - // as count is changed by event data coming in, - // update the host status bar + // as count is changed by jobs coming in, + // update the workflow status bar scope.$watch('count', function(val) { if (val) { Object.keys(val).forEach(key => { - // reposition the hosts status bar by setting + // reposition the workflow status bar by setting // the various flex values to the count of - // those hosts + // those jobs $(`.WorkflowStatusBar-${key}`) .css('flex', `${val[key]} 0 auto`); - // set the tooltip to give how many hosts of + // set the tooltip to give how many jobs of // each type if (val[key] > 0) { scope[`${key}CountTip`] = `${key}${val[key]}`; diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html index e0efddc7b6..c2bc7d87a5 100644 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html +++ b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html @@ -1,26 +1,14 @@
-
-
+
-
-
-
+ aw-tool-tip="{{failedCountTip}}" + data-tip-watch="failedCountTip">