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;
}];