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 }) { function WorkflowNodeHelp({ node, i18n }) {
let nodeType; let nodeType;
if (node.unifiedJobTemplate || node.job) { const job = node?.originalNodeObject?.summary_fields?.job;
if (node.unifiedJobTemplate || job) {
const type = node.unifiedJobTemplate const type = node.unifiedJobTemplate
? node.unifiedJobTemplate.unified_job_type || node.unifiedJobTemplate.type ? node.unifiedJobTemplate.unified_job_type || node.unifiedJobTemplate.type
: node.job.type; : job.type;
switch (type) { switch (type) {
case 'job_template': case 'job_template':
case 'job': case 'job':
@@ -64,8 +65,8 @@ function WorkflowNodeHelp({ node, i18n }) {
} }
let jobStatus; let jobStatus;
if (node.job) { if (job) {
switch (node.job.status) { switch (job.status) {
case 'new': case 'new':
jobStatus = i18n._(t`New`); jobStatus = i18n._(t`New`);
break; break;
@@ -112,23 +113,22 @@ function WorkflowNodeHelp({ node, i18n }) {
return ( return (
<> <>
{!node.unifiedJobTemplate && {!node.unifiedJobTemplate && (!job || job.type !== 'workflow_approval') && (
(!node.job || node.job.type !== 'workflow_approval') && ( <>
<> <ResourceDeleted job={job}>
<ResourceDeleted job={node.job}> <StyledExclamationTriangleIcon />
<StyledExclamationTriangleIcon /> <Trans>
<Trans> The resource associated with this node has been deleted.
The resource associated with this node has been deleted. </Trans>
</Trans> </ResourceDeleted>
</ResourceDeleted> </>
</> )}
)} {job && (
{node.job && (
<GridDL> <GridDL>
<dt> <dt>
<b>{i18n._(t`Name`)}</b> <b>{i18n._(t`Name`)}</b>
</dt> </dt>
<dd id="workflow-node-help-name">{node.job.name}</dd> <dd id="workflow-node-help-name">{job.name}</dd>
<dt> <dt>
<b>{i18n._(t`Type`)}</b> <b>{i18n._(t`Type`)}</b>
</dt> </dt>
@@ -137,19 +137,19 @@ function WorkflowNodeHelp({ node, i18n }) {
<b>{i18n._(t`Job Status`)}</b> <b>{i18n._(t`Job Status`)}</b>
</dt> </dt>
<dd id="workflow-node-help-status">{jobStatus}</dd> <dd id="workflow-node-help-status">{jobStatus}</dd>
{typeof node.job.elapsed === 'number' && ( {typeof job.elapsed === 'number' && (
<> <>
<dt> <dt>
<b>{i18n._(t`Elapsed`)}</b> <b>{i18n._(t`Elapsed`)}</b>
</dt> </dt>
<dd id="workflow-node-help-elapsed"> <dd id="workflow-node-help-elapsed">
{secondsToHHMMSS(node.job.elapsed)} {secondsToHHMMSS(job.elapsed)}
</dd> </dd>
</> </>
)} )}
</GridDL> </GridDL>
)} )}
{node.unifiedJobTemplate && !node.job && ( {node.unifiedJobTemplate && !job && (
<GridDL> <GridDL>
<dt> <dt>
<b>{i18n._(t`Name`)}</b> <b>{i18n._(t`Name`)}</b>
@@ -161,7 +161,7 @@ function WorkflowNodeHelp({ node, i18n }) {
<dd id="workflow-node-help-type">{nodeType}</dd> <dd id="workflow-node-help-type">{nodeType}</dd>
</GridDL> </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> <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, originalNodeObject: node,
}; };
if (node.summary_fields.job) {
nodeObj.job = node.summary_fields.job;
}
if (node.summary_fields.unified_job_template) { if (node.summary_fields.unified_job_template) {
nodeObj.unifiedJobTemplate = 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 }) { function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
const history = useHistory(); const history = useHistory();
const { nodePositions } = useContext(WorkflowStateContext); const { nodePositions } = useContext(WorkflowStateContext);
const job = node?.originalNodeObject?.summary_fields?.job;
let borderColor = '#93969A'; let borderColor = '#93969A';
if (node.job) { if (job) {
if ( if (
node.job.status === 'failed' || job.status === 'failed' ||
node.job.status === 'error' || job.status === 'error' ||
node.job.status === 'canceled' job.status === 'canceled'
) { ) {
borderColor = '#d9534f'; borderColor = '#d9534f';
} }
if (node.job.status === 'successful' || node.job.status === 'ok') { if (job.status === 'successful' || job.status === 'ok') {
borderColor = '#5cb85c'; borderColor = '#5cb85c';
} }
} }
const handleNodeClick = () => { const handleNodeClick = () => {
if (node.job && node.job.type !== 'workflow_aproval') { if (job && job.type !== 'workflow_aproval') {
history.push(`/jobs/${node.job.id}/details`); history.push(`/jobs/${job.id}/details`);
} }
}; };
@@ -90,7 +91,7 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
id={`node-${node.id}`} id={`node-${node.id}`}
transform={`translate(${nodePositions[node.id].x},${nodePositions[node.id] transform={`translate(${nodePositions[node.id].x},${nodePositions[node.id]
.y - nodePositions[1].y})`} .y - nodePositions[1].y})`}
job={node.job} job={job}
onClick={handleNodeClick} onClick={handleNodeClick}
onMouseEnter={mouseEnter} onMouseEnter={mouseEnter}
onMouseLeave={mouseLeave} onMouseLeave={mouseLeave}
@@ -106,14 +107,14 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
/> />
<foreignObject height="58" width="178" x="1" y="1"> <foreignObject height="58" width="178" x="1" y="1">
<NodeContents> <NodeContents>
{node.job ? ( {job ? (
<> <>
<JobTopLine> <JobTopLine>
{node.job.status && <StatusIcon status={node.job.status} />} {job.status && <StatusIcon status={job.status} />}
<p>{node.job.name || node.unifiedJobTemplate.name}</p> <p>{job.name || node.unifiedJobTemplate.name}</p>
</JobTopLine> </JobTopLine>
{!!node?.job?.elapsed && ( {!!job?.elapsed && (
<Elapsed>{secondsToHHMMSS(node.job.elapsed)}</Elapsed> <Elapsed>{secondsToHHMMSS(job.elapsed)}</Elapsed>
)} )}
</> </>
) : ( ) : (
@@ -125,7 +126,7 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
)} )}
</NodeContents> </NodeContents>
</foreignObject> </foreignObject>
{(node.unifiedJobTemplate || node.job) && ( {(node.unifiedJobTemplate || job) && (
<WorkflowNodeTypeLetter node={node} /> <WorkflowNodeTypeLetter node={node} />
)} )}
</NodeG> </NodeG>

View File

@@ -1,5 +1,18 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import useWebsocket from '../../../util/useWebsocket'; 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) { export default function useWsWorkflowOutput(workflowJobId, initialNodes) {
const [nodes, setNodes] = useState(initialNodes); const [nodes, setNodes] = useState(initialNodes);
@@ -14,20 +27,53 @@ export default function useWsWorkflowOutput(workflowJobId, initialNodes) {
useEffect( useEffect(
function parseWsMessage() { function parseWsMessage() {
if ( async function refreshNodeObjects() {
!nodes || const refreshedNodes = [];
nodes.length === 0 || const updatedNodeObjects = await fetchWorkflowNodes(workflowJobId);
lastMessage?.workflow_job_id !== workflowJobId const updatedNodeObjectsMap = updatedNodeObjects.reduce((map, node) => {
) { map[node.id] = node;
return; 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( if (
node => node?.originalNodeObject?.id === lastMessage.workflow_node_id 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) { const index = nodes.findIndex(
setNodes(updateNode(nodes, index, lastMessage)); node => node?.originalNodeObject?.id === lastMessage.workflow_node_id
);
if (index > -1) {
setNodes(updateNode(nodes, index, lastMessage));
}
} }
}, },
[lastMessage] // eslint-disable-line react-hooks/exhaustive-deps [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps