Adds blanket error handling to visualizer save process

This commit is contained in:
mabashian
2020-12-09 15:44:18 -05:00
parent 7a3382dd76
commit ca1e597a4d

View File

@@ -1,17 +1,21 @@
import React, { useEffect, useReducer } from 'react'; import React, { useCallback, useEffect, useReducer } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import styled from 'styled-components'; import styled from 'styled-components';
import { shape } from 'prop-types'; import { shape } from 'prop-types';
import { t } from '@lingui/macro';
import { import {
WorkflowDispatchContext, WorkflowDispatchContext,
WorkflowStateContext, WorkflowStateContext,
} from '../../../contexts/Workflow'; } from '../../../contexts/Workflow';
import { getAddedAndRemoved } from '../../../util/lists'; import { getAddedAndRemoved } from '../../../util/lists';
import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';
import { layoutGraph } from '../../../components/Workflow/WorkflowUtils'; import { layoutGraph } from '../../../components/Workflow/WorkflowUtils';
import ContentError from '../../../components/ContentError'; import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading'; import ContentLoading from '../../../components/ContentLoading';
import workflowReducer from '../../../components/Workflow/workflowReducer'; import workflowReducer from '../../../components/Workflow/workflowReducer';
import useRequest, { useDismissableError } from '../../../util/useRequest';
import { DeleteAllNodesModal, UnsavedChangesModal } from './Modals'; import { DeleteAllNodesModal, UnsavedChangesModal } from './Modals';
import { import {
LinkAddModal, LinkAddModal,
@@ -246,7 +250,49 @@ function Visualizer({ template, i18n }) {
return disassociateNodeRequests; return disassociateNodeRequests;
}; };
const generateLinkMapAndNewLinks = originalLinkMap => { useEffect(() => {
async function fetchData() {
try {
const workflowNodes = await fetchWorkflowNodes(template.id);
dispatch({
type: 'GENERATE_NODES_AND_LINKS',
nodes: workflowNodes,
i18n,
});
} catch (error) {
dispatch({ type: 'SET_CONTENT_ERROR', value: error });
} finally {
dispatch({ type: 'SET_IS_LOADING', value: false });
}
}
fetchData();
}, [template.id, i18n]);
// Update positions of nodes/links
useEffect(() => {
if (nodes) {
const newNodePositions = {};
const nonDeletedNodes = nodes.filter(node => !node.isDeleted);
const g = layoutGraph(nonDeletedNodes, links);
g.nodes().forEach(node => {
newNodePositions[node] = g.node(node);
});
dispatch({ type: 'SET_NODE_POSITIONS', value: newNodePositions });
}
}, [links, nodes]);
const { error: saveVisualizerError, request: saveVisualizer } = useRequest(
useCallback(async () => {
const nodeRequests = [];
const approvalTemplateRequests = [];
const originalLinkMap = {};
const deletedNodeIds = [];
const associateCredentialRequests = [];
const disassociateCredentialRequests = [];
const generateLinkMapAndNewLinks = () => {
const linkMap = {}; const linkMap = {};
const newLinks = []; const newLinks = [];
@@ -294,13 +340,6 @@ function Visualizer({ template, i18n }) {
return [linkMap, newLinks]; return [linkMap, newLinks];
}; };
const handleVisualizerSave = async () => {
const nodeRequests = [];
const approvalTemplateRequests = [];
const originalLinkMap = {};
const deletedNodeIds = [];
const associateCredentialRequests = [];
const disassociateCredentialRequests = [];
nodes.forEach(node => { nodes.forEach(node => {
// node with id=1 is the artificial start node // node with id=1 is the artificial start node
if (node.id === 1) { if (node.id === 1) {
@@ -326,7 +365,9 @@ function Visualizer({ template, i18n }) {
WorkflowJobTemplateNodesAPI.destroy(node.originalNodeObject.id) WorkflowJobTemplateNodesAPI.destroy(node.originalNodeObject.id)
); );
} else if (!node.isDeleted && !node.originalNodeObject) { } else if (!node.isDeleted && !node.originalNodeObject) {
if (node.fullUnifiedJobTemplate.type === 'workflow_approval_template') { if (
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
) {
nodeRequests.push( nodeRequests.push(
WorkflowJobTemplatesAPI.createNode(template.id, {}).then( WorkflowJobTemplatesAPI.createNode(template.id, {}).then(
({ data }) => { ({ data }) => {
@@ -338,11 +379,14 @@ function Visualizer({ template, i18n }) {
always_nodes: [], always_nodes: [],
}; };
approvalTemplateRequests.push( approvalTemplateRequests.push(
WorkflowJobTemplateNodesAPI.createApprovalTemplate(data.id, { WorkflowJobTemplateNodesAPI.createApprovalTemplate(
data.id,
{
name: node.fullUnifiedJobTemplate.name, name: node.fullUnifiedJobTemplate.name,
description: node.fullUnifiedJobTemplate.description, description: node.fullUnifiedJobTemplate.description,
timeout: node.fullUnifiedJobTemplate.timeout, timeout: node.fullUnifiedJobTemplate.timeout,
}) }
)
); );
} }
) )
@@ -385,14 +429,17 @@ function Visualizer({ template, i18n }) {
); );
} }
} else if (node.isEdited) { } else if (node.isEdited) {
if (node.fullUnifiedJobTemplate.type === 'workflow_approval_template') { if (
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
) {
if ( if (
node.originalNodeObject.summary_fields.unified_job_template node.originalNodeObject.summary_fields.unified_job_template
.unified_job_type === 'workflow_approval' .unified_job_type === 'workflow_approval'
) { ) {
approvalTemplateRequests.push( approvalTemplateRequests.push(
WorkflowApprovalTemplatesAPI.update( WorkflowApprovalTemplatesAPI.update(
node.originalNodeObject.summary_fields.unified_job_template.id, node.originalNodeObject.summary_fields.unified_job_template
.id,
{ {
name: node.fullUnifiedJobTemplate.name, name: node.fullUnifiedJobTemplate.name,
description: node.fullUnifiedJobTemplate.description, description: node.fullUnifiedJobTemplate.description,
@@ -418,9 +465,7 @@ function Visualizer({ template, i18n }) {
...node.promptValues, ...node.promptValues,
inventory: node.promptValues?.inventory?.id || null, inventory: node.promptValues?.inventory?.id || null,
unified_job_template: node.fullUnifiedJobTemplate.id, unified_job_template: node.fullUnifiedJobTemplate.id,
}) }).then(() => {
);
const { const {
added: addedCredentials, added: addedCredentials,
removed: removedCredentials, removed: removedCredentials,
@@ -452,6 +497,8 @@ function Visualizer({ template, i18n }) {
) )
); );
} }
})
);
} }
} }
}); });
@@ -470,40 +517,14 @@ function Visualizer({ template, i18n }) {
await Promise.all(associateCredentialRequests); await Promise.all(associateCredentialRequests);
history.push(`/templates/workflow_job_template/${template.id}/details`); history.push(`/templates/workflow_job_template/${template.id}/details`);
}; }, [links, nodes, history, template.id]),
{}
);
useEffect(() => { const {
async function fetchData() { error: nodeRequestError,
try { dismissError: dismissNodeRequestError,
const workflowNodes = await fetchWorkflowNodes(template.id); } = useDismissableError(saveVisualizerError);
dispatch({
type: 'GENERATE_NODES_AND_LINKS',
nodes: workflowNodes,
i18n,
});
} catch (error) {
dispatch({ type: 'SET_CONTENT_ERROR', value: error });
} finally {
dispatch({ type: 'SET_IS_LOADING', value: false });
}
}
fetchData();
}, [template.id, i18n]);
// Update positions of nodes/links
useEffect(() => {
if (nodes) {
const newNodePositions = {};
const nonDeletedNodes = nodes.filter(node => !node.isDeleted);
const g = layoutGraph(nonDeletedNodes, links);
g.nodes().forEach(node => {
newNodePositions[node] = g.node(node);
});
dispatch({ type: 'SET_NODE_POSITIONS', value: newNodePositions });
}
}, [links, nodes]);
if (isLoading) { if (isLoading) {
return ( return (
@@ -529,7 +550,7 @@ function Visualizer({ template, i18n }) {
<Wrapper> <Wrapper>
<VisualizerToolbar <VisualizerToolbar
onClose={handleVisualizerClose} onClose={handleVisualizerClose}
onSave={handleVisualizerSave} onSave={() => saveVisualizer(nodes)}
hasUnsavedChanges={unsavedChanges} hasUnsavedChanges={unsavedChanges}
template={template} template={template}
readOnly={readOnly} readOnly={readOnly}
@@ -553,11 +574,22 @@ function Visualizer({ template, i18n }) {
`/templates/workflow_job_template/${template.id}/details` `/templates/workflow_job_template/${template.id}/details`
) )
} }
onSaveAndExit={() => handleVisualizerSave()} onSaveAndExit={() => saveVisualizer(nodes)}
/> />
)} )}
{showDeleteAllNodesModal && <DeleteAllNodesModal />} {showDeleteAllNodesModal && <DeleteAllNodesModal />}
{nodeToView && <NodeViewModal readOnly={readOnly} />} {nodeToView && <NodeViewModal readOnly={readOnly} />}
{nodeRequestError && (
<AlertModal
isOpen
variant="error"
title={i18n._(t`Error!`)}
onClose={dismissNodeRequestError}
>
{i18n._(t`There was an error saving the workflow.`)}
<ErrorDetail error={nodeRequestError} />
</AlertModal>
)}
</WorkflowDispatchContext.Provider> </WorkflowDispatchContext.Provider>
</WorkflowStateContext.Provider> </WorkflowStateContext.Provider>
); );