mirror of
https://github.com/ansible/awx.git
synced 2026-02-22 13:36:02 -03: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,232 +250,6 @@ function Visualizer({ template, i18n }) {
|
|||||||
return disassociateNodeRequests;
|
return disassociateNodeRequests;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateLinkMapAndNewLinks = originalLinkMap => {
|
|
||||||
const linkMap = {};
|
|
||||||
const newLinks = [];
|
|
||||||
|
|
||||||
links.forEach(link => {
|
|
||||||
if (link.source.id !== 1) {
|
|
||||||
const realLinkSourceId = originalLinkMap[link.source.id].id;
|
|
||||||
const realLinkTargetId = originalLinkMap[link.target.id].id;
|
|
||||||
if (!linkMap[realLinkSourceId]) {
|
|
||||||
linkMap[realLinkSourceId] = {};
|
|
||||||
}
|
|
||||||
linkMap[realLinkSourceId][realLinkTargetId] = link.linkType;
|
|
||||||
switch (link.linkType) {
|
|
||||||
case 'success':
|
|
||||||
if (
|
|
||||||
!originalLinkMap[link.source.id].success_nodes.includes(
|
|
||||||
originalLinkMap[link.target.id].id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newLinks.push(link);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'failure':
|
|
||||||
if (
|
|
||||||
!originalLinkMap[link.source.id].failure_nodes.includes(
|
|
||||||
originalLinkMap[link.target.id].id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newLinks.push(link);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'always':
|
|
||||||
if (
|
|
||||||
!originalLinkMap[link.source.id].always_nodes.includes(
|
|
||||||
originalLinkMap[link.target.id].id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newLinks.push(link);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [linkMap, newLinks];
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleVisualizerSave = async () => {
|
|
||||||
const nodeRequests = [];
|
|
||||||
const approvalTemplateRequests = [];
|
|
||||||
const originalLinkMap = {};
|
|
||||||
const deletedNodeIds = [];
|
|
||||||
const associateCredentialRequests = [];
|
|
||||||
const disassociateCredentialRequests = [];
|
|
||||||
nodes.forEach(node => {
|
|
||||||
// node with id=1 is the artificial start node
|
|
||||||
if (node.id === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (node.originalNodeObject && !node.isDeleted) {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
success_nodes,
|
|
||||||
failure_nodes,
|
|
||||||
always_nodes,
|
|
||||||
} = node.originalNodeObject;
|
|
||||||
originalLinkMap[node.id] = {
|
|
||||||
id,
|
|
||||||
success_nodes,
|
|
||||||
failure_nodes,
|
|
||||||
always_nodes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (node.isDeleted && node.originalNodeObject) {
|
|
||||||
deletedNodeIds.push(node.originalNodeObject.id);
|
|
||||||
nodeRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.destroy(node.originalNodeObject.id)
|
|
||||||
);
|
|
||||||
} else if (!node.isDeleted && !node.originalNodeObject) {
|
|
||||||
if (node.fullUnifiedJobTemplate.type === 'workflow_approval_template') {
|
|
||||||
nodeRequests.push(
|
|
||||||
WorkflowJobTemplatesAPI.createNode(template.id, {}).then(
|
|
||||||
({ data }) => {
|
|
||||||
node.originalNodeObject = data;
|
|
||||||
originalLinkMap[node.id] = {
|
|
||||||
id: data.id,
|
|
||||||
success_nodes: [],
|
|
||||||
failure_nodes: [],
|
|
||||||
always_nodes: [],
|
|
||||||
};
|
|
||||||
approvalTemplateRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.createApprovalTemplate(data.id, {
|
|
||||||
name: node.fullUnifiedJobTemplate.name,
|
|
||||||
description: node.fullUnifiedJobTemplate.description,
|
|
||||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
nodeRequests.push(
|
|
||||||
WorkflowJobTemplatesAPI.createNode(template.id, {
|
|
||||||
...node.promptValues,
|
|
||||||
inventory: node.promptValues?.inventory?.id || null,
|
|
||||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
|
||||||
}).then(({ data }) => {
|
|
||||||
node.originalNodeObject = data;
|
|
||||||
originalLinkMap[node.id] = {
|
|
||||||
id: data.id,
|
|
||||||
success_nodes: [],
|
|
||||||
failure_nodes: [],
|
|
||||||
always_nodes: [],
|
|
||||||
};
|
|
||||||
if (node.promptValues?.removedCredentials?.length > 0) {
|
|
||||||
node.promptValues.removedCredentials.forEach(cred => {
|
|
||||||
disassociateCredentialRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.disassociateCredentials(
|
|
||||||
data.id,
|
|
||||||
cred.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (node.promptValues?.addedCredentials?.length > 0) {
|
|
||||||
node.promptValues.addedCredentials.forEach(cred => {
|
|
||||||
associateCredentialRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.associateCredentials(
|
|
||||||
data.id,
|
|
||||||
cred.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (node.isEdited) {
|
|
||||||
if (node.fullUnifiedJobTemplate.type === 'workflow_approval_template') {
|
|
||||||
if (
|
|
||||||
node.originalNodeObject.summary_fields.unified_job_template
|
|
||||||
.unified_job_type === 'workflow_approval'
|
|
||||||
) {
|
|
||||||
approvalTemplateRequests.push(
|
|
||||||
WorkflowApprovalTemplatesAPI.update(
|
|
||||||
node.originalNodeObject.summary_fields.unified_job_template.id,
|
|
||||||
{
|
|
||||||
name: node.fullUnifiedJobTemplate.name,
|
|
||||||
description: node.fullUnifiedJobTemplate.description,
|
|
||||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
approvalTemplateRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.createApprovalTemplate(
|
|
||||||
node.originalNodeObject.id,
|
|
||||||
{
|
|
||||||
name: node.fullUnifiedJobTemplate.name,
|
|
||||||
description: node.fullUnifiedJobTemplate.description,
|
|
||||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nodeRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.replace(node.originalNodeObject.id, {
|
|
||||||
...node.promptValues,
|
|
||||||
inventory: node.promptValues?.inventory?.id || null,
|
|
||||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
added: addedCredentials,
|
|
||||||
removed: removedCredentials,
|
|
||||||
} = getAddedAndRemoved(
|
|
||||||
getAggregatedCredentials(
|
|
||||||
node?.originalNodeCredentials,
|
|
||||||
node.launchConfig?.defaults?.credentials
|
|
||||||
),
|
|
||||||
node.promptValues?.credentials
|
|
||||||
);
|
|
||||||
|
|
||||||
if (addedCredentials.length > 0) {
|
|
||||||
addedCredentials.forEach(cred => {
|
|
||||||
associateCredentialRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.associateCredentials(
|
|
||||||
node.originalNodeObject.id,
|
|
||||||
cred.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (removedCredentials?.length > 0) {
|
|
||||||
removedCredentials.forEach(cred =>
|
|
||||||
disassociateCredentialRequests.push(
|
|
||||||
WorkflowJobTemplateNodesAPI.disassociateCredentials(
|
|
||||||
node.originalNodeObject.id,
|
|
||||||
cred.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(nodeRequests);
|
|
||||||
// Creating approval templates needs to happen after the node has been created
|
|
||||||
// since we reference the node in the approval template request.
|
|
||||||
await Promise.all(approvalTemplateRequests);
|
|
||||||
const [linkMap, newLinks] = generateLinkMapAndNewLinks(originalLinkMap);
|
|
||||||
await Promise.all(
|
|
||||||
disassociateNodes(originalLinkMap, deletedNodeIds, linkMap)
|
|
||||||
);
|
|
||||||
await Promise.all(associateNodes(newLinks, originalLinkMap));
|
|
||||||
|
|
||||||
await Promise.all(disassociateCredentialRequests);
|
|
||||||
await Promise.all(associateCredentialRequests);
|
|
||||||
|
|
||||||
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
@@ -505,6 +283,249 @@ function Visualizer({ template, i18n }) {
|
|||||||
}
|
}
|
||||||
}, [links, nodes]);
|
}, [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 newLinks = [];
|
||||||
|
|
||||||
|
links.forEach(link => {
|
||||||
|
if (link.source.id !== 1) {
|
||||||
|
const realLinkSourceId = originalLinkMap[link.source.id].id;
|
||||||
|
const realLinkTargetId = originalLinkMap[link.target.id].id;
|
||||||
|
if (!linkMap[realLinkSourceId]) {
|
||||||
|
linkMap[realLinkSourceId] = {};
|
||||||
|
}
|
||||||
|
linkMap[realLinkSourceId][realLinkTargetId] = link.linkType;
|
||||||
|
switch (link.linkType) {
|
||||||
|
case 'success':
|
||||||
|
if (
|
||||||
|
!originalLinkMap[link.source.id].success_nodes.includes(
|
||||||
|
originalLinkMap[link.target.id].id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newLinks.push(link);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'failure':
|
||||||
|
if (
|
||||||
|
!originalLinkMap[link.source.id].failure_nodes.includes(
|
||||||
|
originalLinkMap[link.target.id].id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newLinks.push(link);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'always':
|
||||||
|
if (
|
||||||
|
!originalLinkMap[link.source.id].always_nodes.includes(
|
||||||
|
originalLinkMap[link.target.id].id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newLinks.push(link);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [linkMap, newLinks];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
// node with id=1 is the artificial start node
|
||||||
|
if (node.id === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.originalNodeObject && !node.isDeleted) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
success_nodes,
|
||||||
|
failure_nodes,
|
||||||
|
always_nodes,
|
||||||
|
} = node.originalNodeObject;
|
||||||
|
originalLinkMap[node.id] = {
|
||||||
|
id,
|
||||||
|
success_nodes,
|
||||||
|
failure_nodes,
|
||||||
|
always_nodes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (node.isDeleted && node.originalNodeObject) {
|
||||||
|
deletedNodeIds.push(node.originalNodeObject.id);
|
||||||
|
nodeRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.destroy(node.originalNodeObject.id)
|
||||||
|
);
|
||||||
|
} else if (!node.isDeleted && !node.originalNodeObject) {
|
||||||
|
if (
|
||||||
|
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
|
||||||
|
) {
|
||||||
|
nodeRequests.push(
|
||||||
|
WorkflowJobTemplatesAPI.createNode(template.id, {}).then(
|
||||||
|
({ data }) => {
|
||||||
|
node.originalNodeObject = data;
|
||||||
|
originalLinkMap[node.id] = {
|
||||||
|
id: data.id,
|
||||||
|
success_nodes: [],
|
||||||
|
failure_nodes: [],
|
||||||
|
always_nodes: [],
|
||||||
|
};
|
||||||
|
approvalTemplateRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.createApprovalTemplate(
|
||||||
|
data.id,
|
||||||
|
{
|
||||||
|
name: node.fullUnifiedJobTemplate.name,
|
||||||
|
description: node.fullUnifiedJobTemplate.description,
|
||||||
|
timeout: node.fullUnifiedJobTemplate.timeout,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
nodeRequests.push(
|
||||||
|
WorkflowJobTemplatesAPI.createNode(template.id, {
|
||||||
|
...node.promptValues,
|
||||||
|
inventory: node.promptValues?.inventory?.id || null,
|
||||||
|
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||||
|
}).then(({ data }) => {
|
||||||
|
node.originalNodeObject = data;
|
||||||
|
originalLinkMap[node.id] = {
|
||||||
|
id: data.id,
|
||||||
|
success_nodes: [],
|
||||||
|
failure_nodes: [],
|
||||||
|
always_nodes: [],
|
||||||
|
};
|
||||||
|
if (node.promptValues?.removedCredentials?.length > 0) {
|
||||||
|
node.promptValues.removedCredentials.forEach(cred => {
|
||||||
|
disassociateCredentialRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.disassociateCredentials(
|
||||||
|
data.id,
|
||||||
|
cred.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.promptValues?.addedCredentials?.length > 0) {
|
||||||
|
node.promptValues.addedCredentials.forEach(cred => {
|
||||||
|
associateCredentialRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.associateCredentials(
|
||||||
|
data.id,
|
||||||
|
cred.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (node.isEdited) {
|
||||||
|
if (
|
||||||
|
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
node.originalNodeObject.summary_fields.unified_job_template
|
||||||
|
.unified_job_type === 'workflow_approval'
|
||||||
|
) {
|
||||||
|
approvalTemplateRequests.push(
|
||||||
|
WorkflowApprovalTemplatesAPI.update(
|
||||||
|
node.originalNodeObject.summary_fields.unified_job_template
|
||||||
|
.id,
|
||||||
|
{
|
||||||
|
name: node.fullUnifiedJobTemplate.name,
|
||||||
|
description: node.fullUnifiedJobTemplate.description,
|
||||||
|
timeout: node.fullUnifiedJobTemplate.timeout,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
approvalTemplateRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.createApprovalTemplate(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
{
|
||||||
|
name: node.fullUnifiedJobTemplate.name,
|
||||||
|
description: node.fullUnifiedJobTemplate.description,
|
||||||
|
timeout: node.fullUnifiedJobTemplate.timeout,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodeRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.replace(node.originalNodeObject.id, {
|
||||||
|
...node.promptValues,
|
||||||
|
inventory: node.promptValues?.inventory?.id || null,
|
||||||
|
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||||
|
}).then(() => {
|
||||||
|
const {
|
||||||
|
added: addedCredentials,
|
||||||
|
removed: removedCredentials,
|
||||||
|
} = getAddedAndRemoved(
|
||||||
|
getAggregatedCredentials(
|
||||||
|
node?.originalNodeCredentials,
|
||||||
|
node.launchConfig?.defaults?.credentials
|
||||||
|
),
|
||||||
|
node.promptValues?.credentials
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addedCredentials.length > 0) {
|
||||||
|
addedCredentials.forEach(cred => {
|
||||||
|
associateCredentialRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.associateCredentials(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
cred.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (removedCredentials?.length > 0) {
|
||||||
|
removedCredentials.forEach(cred =>
|
||||||
|
disassociateCredentialRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.disassociateCredentials(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
cred.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(nodeRequests);
|
||||||
|
// Creating approval templates needs to happen after the node has been created
|
||||||
|
// since we reference the node in the approval template request.
|
||||||
|
await Promise.all(approvalTemplateRequests);
|
||||||
|
const [linkMap, newLinks] = generateLinkMapAndNewLinks(originalLinkMap);
|
||||||
|
await Promise.all(
|
||||||
|
disassociateNodes(originalLinkMap, deletedNodeIds, linkMap)
|
||||||
|
);
|
||||||
|
await Promise.all(associateNodes(newLinks, originalLinkMap));
|
||||||
|
|
||||||
|
await Promise.all(disassociateCredentialRequests);
|
||||||
|
await Promise.all(associateCredentialRequests);
|
||||||
|
|
||||||
|
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
||||||
|
}, [links, nodes, history, template.id]),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
error: nodeRequestError,
|
||||||
|
dismissError: dismissNodeRequestError,
|
||||||
|
} = useDismissableError(saveVisualizerError);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<CenteredContent>
|
<CenteredContent>
|
||||||
@@ -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