diff --git a/awx/ui/client/features/output/legacy.route.js b/awx/ui/client/features/output/legacy.route.js index 2db75d01d7..838cc5c25d 100644 --- a/awx/ui/client/features/output/legacy.route.js +++ b/awx/ui/client/features/output/legacy.route.js @@ -1,4 +1,4 @@ -function LegacyRedirect ($stateRegistry) { +function LegacyRedirect ($http, $stateRegistry) { const destination = 'output'; const routes = [ { @@ -66,11 +66,43 @@ function LegacyRedirect ($stateRegistry) { return { state: 'schedules.edit', params: { schedule_id, schedule_search } }; } }, + { + name: 'workflowNodeRedirect', + url: '/workflow_node_results/:id', + redirectTo: (trans) => { + // The workflow job viewer uses this route for playbook job nodes. The provided id + // is used to lookup the corresponding unified job, which is then inspected to + // determine if we need to redirect to a split (workflow) job or a playbook job. + const { id } = trans.params(); + const endpoint = '/api/v2/unified_jobs/'; + + return $http.get(endpoint, { params: { id } }) + .then(({ data }) => { + const { results } = data; + const [obj] = results; + + if (obj) { + if (obj.type === 'workflow_job') { + return { state: 'workflowResults', params: { id } }; + } else if (obj.type === 'job') { + return { state: 'output', params: { type: 'playbook', id } }; + } else if (obj.type === 'inventory_update') { + return { state: 'output', params: { type: 'inventory', id } }; + } else if (obj.type === 'project_update') { + return { state: 'output', params: { type: 'project', id } }; + } + } + + return { state: 'jobs' }; + }) + .catch(() => ({ state: 'dashboard' })); + } + }, ]; routes.forEach(state => $stateRegistry.register(state)); } -LegacyRedirect.$inject = ['$stateRegistry']; +LegacyRedirect.$inject = ['$http', '$stateRegistry']; export default LegacyRedirect; diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index 375b2b5acd..18275d4655 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -764,7 +764,14 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge }); baseSvg.selectAll(".WorkflowChart-detailsLink") - .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; }); + .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; }) + .html(function (d) { + let href = ""; + if (d.job) { + href = `/#/workflow_node_results/${d.job.id}`; + } + return `${TemplatesStrings.get('workflow_maker.DETAILS')}`; + }); baseSvg.selectAll(".WorkflowChart-deletedText") .style("display", function(d){ return d.unifiedJobTemplate || d.id === scope.graphState.nodeBeingAdded ? "none" : null; }); @@ -1043,16 +1050,24 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .classed("WorkflowChart-nodeHovering", false); } }); - thisNode.append("text") + thisNode.append("foreignObject") .attr("x", nodeW - 45) - .attr("y", nodeH - 10) + .attr("y", nodeH - 15) + .attr("height", "15px") + .attr("width", "40px") .attr("dy", ".35em") .attr("class", "WorkflowChart-detailsLink") .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; }) - .text(function () { - return TemplatesStrings.get('workflow_maker.DETAILS'); + .on("mousedown", function(){ + d3.event.stopPropagation(); }) - .call(details); + .html(function (d) { + let href = ""; + if (d.job) { + href = `/#/workflow_node_results/${d.job.id}`; + } + return `${TemplatesStrings.get('workflow_maker.DETAILS')}`; + }); thisNode.append("circle") .attr("id", function(d){return "node-" + d.id + "-add";}) .attr("cx", nodeW) @@ -1328,30 +1343,6 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge }); } - function details() { - this.on("mouseover", function() { - d3.select(this).style("text-decoration", "underline"); - }); - this.on("mouseout", function() { - d3.select(this).style("text-decoration", null); - }); - this.on("click", function(d) { - if(d.job.type === 'job') { - $state.go('output', {id: d.job.id, type: 'playbook'}); - } - else if(d.job.type === 'inventory_update') { - $state.go('output', {id: d.job.id, type: 'inventory'}); - } - else if(d.job.type === 'project_update') { - $state.go('output', {id: d.job.id, type: 'project'}); - } else if (d.job.type === 'workflow_job') { - $state.go('workflowResults', { - id: d.job.id, - }); - } - }); - } - scope.$on('refreshWorkflowChart', function(){ if(scope.graphState) { updateGraph(); 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 0ec32ad69c..9870d2c7b1 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ b/awx/ui/client/src/workflow-results/workflow-results.controller.js @@ -281,13 +281,12 @@ export default ['workflowData', 'workflowResultsService', 'workflowDataOptions', if (nodeRef[node.id] && nodeRef[node.id].originalNodeObject.id === data.workflow_node_id) { node.job = { id: data.unified_job_id, - status: data.status, - type: nodeRef[node.id].unifiedJobTemplate.unified_job_type + status: data.status }; + + $scope.$broadcast("refreshWorkflowChart"); } }); - - $scope.$broadcast("refreshWorkflowChart"); } getLabelsAndTooltips(); }); 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 0db1f7a78c..6fbfc59371 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.service.js +++ b/awx/ui/client/src/workflow-results/workflow-results.service.js @@ -132,7 +132,7 @@ export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErr return true; } return false; - }, + } }; return val; }];