Refresh nodes after workflow has finished running so that we can display all job info for relevant nodes.

This commit is contained in:
mabashian
2020-08-18 15:38:35 -04:00
parent e28c9bb3c4
commit c318a17590
4 changed files with 93 additions and 49 deletions

View File

@@ -33,10 +33,11 @@ const StyledExclamationTriangleIcon = styled(ExclamationTriangleIcon)`
function WorkflowNodeHelp({ node, i18n }) {
let nodeType;
if (node.unifiedJobTemplate || node.job) {
const job = node?.originalNodeObject?.summary_fields?.job;
if (node.unifiedJobTemplate || job) {
const type = node.unifiedJobTemplate
? node.unifiedJobTemplate.unified_job_type || node.unifiedJobTemplate.type
: node.job.type;
: job.type;
switch (type) {
case 'job_template':
case 'job':
@@ -64,8 +65,8 @@ function WorkflowNodeHelp({ node, i18n }) {
}
let jobStatus;
if (node.job) {
switch (node.job.status) {
if (job) {
switch (job.status) {
case 'new':
jobStatus = i18n._(t`New`);
break;
@@ -112,23 +113,22 @@ function WorkflowNodeHelp({ node, i18n }) {
return (
<>
{!node.unifiedJobTemplate &&
(!node.job || node.job.type !== 'workflow_approval') && (
<>
<ResourceDeleted job={node.job}>
<StyledExclamationTriangleIcon />
<Trans>
The resource associated with this node has been deleted.
</Trans>
</ResourceDeleted>
</>
)}
{node.job && (
{!node.unifiedJobTemplate && (!job || job.type !== 'workflow_approval') && (
<>
<ResourceDeleted job={job}>
<StyledExclamationTriangleIcon />
<Trans>
The resource associated with this node has been deleted.
</Trans>
</ResourceDeleted>
</>
)}
{job && (
<GridDL>
<dt>
<b>{i18n._(t`Name`)}</b>
</dt>
<dd id="workflow-node-help-name">{node.job.name}</dd>
<dd id="workflow-node-help-name">{job.name}</dd>
<dt>
<b>{i18n._(t`Type`)}</b>
</dt>
@@ -137,19 +137,19 @@ function WorkflowNodeHelp({ node, i18n }) {
<b>{i18n._(t`Job Status`)}</b>
</dt>
<dd id="workflow-node-help-status">{jobStatus}</dd>
{typeof node.job.elapsed === 'number' && (
{typeof job.elapsed === 'number' && (
<>
<dt>
<b>{i18n._(t`Elapsed`)}</b>
</dt>
<dd id="workflow-node-help-elapsed">
{secondsToHHMMSS(node.job.elapsed)}
{secondsToHHMMSS(job.elapsed)}
</dd>
</>
)}
</GridDL>
)}
{node.unifiedJobTemplate && !node.job && (
{node.unifiedJobTemplate && !job && (
<GridDL>
<dt>
<b>{i18n._(t`Name`)}</b>
@@ -161,7 +161,7 @@ function WorkflowNodeHelp({ node, i18n }) {
<dd id="workflow-node-help-type">{nodeType}</dd>
</GridDL>
)}
{node.job && node.job.type !== 'workflow_approval' && (
{job && job.type !== 'workflow_approval' && (
<p css="margin-top: 10px">{i18n._(t`Click to view job details`)}</p>
)}
</>

View File

@@ -365,9 +365,6 @@ function generateNodes(workflowNodes, i18n) {
originalNodeObject: node,
};
if (node.summary_fields.job) {
nodeObj.job = node.summary_fields.job;
}
if (node.summary_fields.unified_job_template) {
nodeObj.unifiedJobTemplate = node.summary_fields.unified_job_template;
}

View File

@@ -64,24 +64,25 @@ Elapsed.displayName = 'Elapsed';
function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
const history = useHistory();
const { nodePositions } = useContext(WorkflowStateContext);
const job = node?.originalNodeObject?.summary_fields?.job;
let borderColor = '#93969A';
if (node.job) {
if (job) {
if (
node.job.status === 'failed' ||
node.job.status === 'error' ||
node.job.status === 'canceled'
job.status === 'failed' ||
job.status === 'error' ||
job.status === 'canceled'
) {
borderColor = '#d9534f';
}
if (node.job.status === 'successful' || node.job.status === 'ok') {
if (job.status === 'successful' || job.status === 'ok') {
borderColor = '#5cb85c';
}
}
const handleNodeClick = () => {
if (node.job && node.job.type !== 'workflow_aproval') {
history.push(`/jobs/${node.job.id}/details`);
if (job && job.type !== 'workflow_aproval') {
history.push(`/jobs/${job.id}/details`);
}
};
@@ -90,7 +91,7 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
id={`node-${node.id}`}
transform={`translate(${nodePositions[node.id].x},${nodePositions[node.id]
.y - nodePositions[1].y})`}
job={node.job}
job={job}
onClick={handleNodeClick}
onMouseEnter={mouseEnter}
onMouseLeave={mouseLeave}
@@ -106,14 +107,14 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
/>
<foreignObject height="58" width="178" x="1" y="1">
<NodeContents>
{node.job ? (
{job ? (
<>
<JobTopLine>
{node.job.status && <StatusIcon status={node.job.status} />}
<p>{node.job.name || node.unifiedJobTemplate.name}</p>
{job.status && <StatusIcon status={job.status} />}
<p>{job.name || node.unifiedJobTemplate.name}</p>
</JobTopLine>
{!!node?.job?.elapsed && (
<Elapsed>{secondsToHHMMSS(node.job.elapsed)}</Elapsed>
{!!job?.elapsed && (
<Elapsed>{secondsToHHMMSS(job.elapsed)}</Elapsed>
)}
</>
) : (
@@ -125,7 +126,7 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
)}
</NodeContents>
</foreignObject>
{(node.unifiedJobTemplate || node.job) && (
{(node.unifiedJobTemplate || job) && (
<WorkflowNodeTypeLetter node={node} />
)}
</NodeG>

View File

@@ -1,5 +1,18 @@
import { useState, useEffect } from 'react';
import useWebsocket from '../../../util/useWebsocket';
import { WorkflowJobsAPI } from '../../../api';
const fetchWorkflowNodes = async (jobId, pageNo = 1, nodes = []) => {
const { data } = await WorkflowJobsAPI.readNodes(jobId, {
page_size: 200,
page: pageNo,
});
if (data.next) {
return fetchWorkflowNodes(jobId, pageNo + 1, nodes.concat(data.results));
}
return nodes.concat(data.results);
};
export default function useWsWorkflowOutput(workflowJobId, initialNodes) {
const [nodes, setNodes] = useState(initialNodes);
@@ -14,20 +27,53 @@ export default function useWsWorkflowOutput(workflowJobId, initialNodes) {
useEffect(
function parseWsMessage() {
if (
!nodes ||
nodes.length === 0 ||
lastMessage?.workflow_job_id !== workflowJobId
) {
return;
async function refreshNodeObjects() {
const refreshedNodes = [];
const updatedNodeObjects = await fetchWorkflowNodes(workflowJobId);
const updatedNodeObjectsMap = updatedNodeObjects.reduce((map, node) => {
map[node.id] = node;
return map;
}, {});
nodes.forEach(node => {
if (node.id === 1) {
// This is our artificial start node
refreshedNodes.push({
...node,
});
} else {
refreshedNodes.push({
...node,
originalNodeObject:
updatedNodeObjectsMap[node.originalNodeObject.id],
});
}
});
setNodes(refreshedNodes);
}
const index = nodes.findIndex(
node => node?.originalNodeObject?.id === lastMessage.workflow_node_id
);
if (
lastMessage?.unified_job_id === workflowJobId &&
['successful', 'failed', 'error', 'cancelled'].includes(
lastMessage.status
)
) {
refreshNodeObjects();
} else {
if (
!nodes ||
nodes.length === 0 ||
lastMessage?.workflow_job_id !== workflowJobId
) {
return;
}
if (index > -1) {
setNodes(updateNode(nodes, index, lastMessage));
const index = nodes.findIndex(
node => node?.originalNodeObject?.id === lastMessage.workflow_node_id
);
if (index > -1) {
setNodes(updateNode(nodes, index, lastMessage));
}
}
},
[lastMessage] // eslint-disable-line react-hooks/exhaustive-deps