mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 13:25:02 -02:30
Adds blanket error handling to visualizer save process
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user