mirror of
https://github.com/ansible/awx.git
synced 2026-01-19 21:51:26 -03:30
add convergence to workflows
This commit is contained in:
parent
2594b90565
commit
225c3263d0
@ -183,6 +183,7 @@ function createNode(state, node) {
|
||||
fullUnifiedJobTemplate: node.nodeResource,
|
||||
isInvalidLinkTarget: false,
|
||||
promptValues: node.promptValues,
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
});
|
||||
|
||||
// Ensures that root nodes appear to always run
|
||||
@ -657,10 +658,19 @@ function updateLink(state, linkType) {
|
||||
|
||||
function updateNode(state, editedNode) {
|
||||
const { nodeToEdit, nodes } = state;
|
||||
const { nodeResource, launchConfig, promptValues } = editedNode;
|
||||
const {
|
||||
nodeResource,
|
||||
launchConfig,
|
||||
promptValues,
|
||||
all_parents_must_converge,
|
||||
} = editedNode;
|
||||
const newNodes = [...nodes];
|
||||
|
||||
const matchingNode = newNodes.find(node => node.id === nodeToEdit.id);
|
||||
matchingNode.all_parents_must_converge = all_parents_must_converge;
|
||||
if (matchingNode.originalNodeObject) {
|
||||
delete matchingNode.originalNodeObject.all_parents_must_converge;
|
||||
}
|
||||
matchingNode.fullUnifiedJobTemplate = nodeResource;
|
||||
matchingNode.isEdited = true;
|
||||
matchingNode.launchConfig = launchConfig;
|
||||
|
||||
@ -59,6 +59,11 @@ const NodeDefaultLabel = styled.p`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const ConvergenceLabel = styled.p`
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
`;
|
||||
|
||||
Elapsed.displayName = 'Elapsed';
|
||||
|
||||
function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
|
||||
@ -100,6 +105,30 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
|
||||
onMouseEnter={mouseEnter}
|
||||
onMouseLeave={mouseLeave}
|
||||
>
|
||||
{(node.all_parents_must_converge ||
|
||||
node?.originalNodeObject?.all_parents_must_converge) && (
|
||||
<>
|
||||
<rect
|
||||
fill={borderColor}
|
||||
height={wfConstants.nodeH / 4}
|
||||
rx={2}
|
||||
ry={2}
|
||||
x={wfConstants.nodeW / 2 - wfConstants.nodeW / 10}
|
||||
y={-wfConstants.nodeH / 4 + 2}
|
||||
stroke={borderColor}
|
||||
strokeWidth="2px"
|
||||
width={wfConstants.nodeW / 5}
|
||||
/>
|
||||
<foreignObject
|
||||
height={wfConstants.nodeH / 4}
|
||||
width={wfConstants.nodeW / 5}
|
||||
x={wfConstants.nodeW / 2 - wfConstants.nodeW / 10 + 7}
|
||||
y={-wfConstants.nodeH / 4 - 1}
|
||||
>
|
||||
<ConvergenceLabel>{i18n._(t`ALL`)}</ConvergenceLabel>
|
||||
</foreignObject>
|
||||
</>
|
||||
)}
|
||||
<rect
|
||||
fill="#FFFFFF"
|
||||
height={wfConstants.nodeH}
|
||||
|
||||
@ -19,6 +19,7 @@ function NodeAddModal({ i18n }) {
|
||||
timeoutMinutes,
|
||||
timeoutSeconds,
|
||||
linkType,
|
||||
convergence,
|
||||
} = values;
|
||||
|
||||
if (values) {
|
||||
@ -33,8 +34,11 @@ function NodeAddModal({ i18n }) {
|
||||
|
||||
const node = {
|
||||
linkType,
|
||||
all_parents_must_converge: convergence === 'all',
|
||||
};
|
||||
|
||||
delete values.convergence;
|
||||
|
||||
delete values.linkType;
|
||||
|
||||
if (values.nodeType === 'workflow_approval_template') {
|
||||
|
||||
@ -48,6 +48,7 @@ describe('NodeAddModal', () => {
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
node: {
|
||||
all_parents_must_converge: false,
|
||||
linkType: 'success',
|
||||
nodeResource: {
|
||||
id: 448,
|
||||
|
||||
@ -17,11 +17,13 @@ function NodeEditModal({ i18n }) {
|
||||
nodeType,
|
||||
timeoutMinutes,
|
||||
timeoutSeconds,
|
||||
convergence,
|
||||
...rest
|
||||
} = values;
|
||||
let node;
|
||||
if (values.nodeType === 'workflow_approval_template') {
|
||||
node = {
|
||||
all_parents_must_converge: convergence === 'all',
|
||||
nodeResource: {
|
||||
description: approvalDescription,
|
||||
name: approvalName,
|
||||
@ -32,6 +34,7 @@ function NodeEditModal({ i18n }) {
|
||||
} else {
|
||||
node = {
|
||||
nodeResource,
|
||||
all_parents_must_converge: convergence === 'all',
|
||||
};
|
||||
if (nodeType === 'job_template' || nodeType === 'workflow_job_template') {
|
||||
node.promptValues = {
|
||||
|
||||
@ -63,6 +63,7 @@ describe('NodeEditModal', () => {
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
node: {
|
||||
all_parents_must_converge: false,
|
||||
nodeResource: { id: 448, name: 'Test JT', type: 'job_template' },
|
||||
},
|
||||
type: 'UPDATE_NODE',
|
||||
|
||||
@ -101,7 +101,6 @@ function NodeModalForm({
|
||||
values.extra_data = extraVars && parseVariableField(extraVars);
|
||||
delete values.extra_vars;
|
||||
}
|
||||
|
||||
onSave(values, launchConfig);
|
||||
};
|
||||
|
||||
@ -357,6 +356,7 @@ const NodeModal = ({ onSave, i18n, askLinkType, title }) => {
|
||||
approvalDescription: '',
|
||||
timeoutMinutes: 0,
|
||||
timeoutSeconds: 0,
|
||||
convergence: 'any',
|
||||
linkType: 'success',
|
||||
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
||||
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
||||
|
||||
@ -307,6 +307,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
linkType: 'always',
|
||||
nodeType: 'job_template',
|
||||
inventory: { name: 'Foo Inv', id: 1 },
|
||||
@ -345,6 +346,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
linkType: 'failure',
|
||||
nodeResource: {
|
||||
id: 1,
|
||||
@ -383,6 +385,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
linkType: 'failure',
|
||||
nodeResource: {
|
||||
id: 1,
|
||||
@ -422,6 +425,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
linkType: 'success',
|
||||
nodeResource: {
|
||||
id: 1,
|
||||
@ -506,6 +510,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
approvalDescription: 'Test Approval Description',
|
||||
approvalName: 'Test Approval',
|
||||
linkType: 'always',
|
||||
@ -605,6 +610,7 @@ describe('NodeModal', () => {
|
||||
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
approvalDescription: 'Test Approval Description',
|
||||
approvalName: 'Test Approval',
|
||||
linkType: 'success',
|
||||
@ -668,6 +674,7 @@ describe('NodeModal', () => {
|
||||
});
|
||||
expect(onSave).toBeCalledWith(
|
||||
{
|
||||
convergence: 'any',
|
||||
linkType: 'success',
|
||||
nodeResource: {
|
||||
id: 1,
|
||||
|
||||
@ -1,13 +1,25 @@
|
||||
import 'styled-components/macro';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t, Trans } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
import { useField } from 'formik';
|
||||
import { Alert, Form, FormGroup, TextInput } from '@patternfly/react-core';
|
||||
import {
|
||||
Alert,
|
||||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
Select,
|
||||
SelectVariant,
|
||||
SelectOption,
|
||||
} from '@patternfly/react-core';
|
||||
import { required } from '../../../../../../util/validators';
|
||||
|
||||
import { FormFullWidthLayout } from '../../../../../../components/FormLayout';
|
||||
import {
|
||||
FormColumnLayout,
|
||||
FormFullWidthLayout,
|
||||
} from '../../../../../../components/FormLayout';
|
||||
import Popover from '../../../../../../components/Popover';
|
||||
import AnsibleSelect from '../../../../../../components/AnsibleSelect';
|
||||
import InventorySourcesList from './InventorySourcesList';
|
||||
import JobTemplatesList from './JobTemplatesList';
|
||||
@ -44,6 +56,9 @@ function NodeTypeStep({ i18n }) {
|
||||
const [timeoutSecondsField, , timeoutSecondsHelpers] = useField(
|
||||
'timeoutSeconds'
|
||||
);
|
||||
const [convergenceField, , convergenceFieldHelpers] = useField('convergence');
|
||||
|
||||
const [isConvergenceOpen, setIsConvergenceOpen] = useState(false);
|
||||
|
||||
const isValid = !approvalNameMeta.touched || !approvalNameMeta.error;
|
||||
return (
|
||||
@ -101,6 +116,7 @@ function NodeTypeStep({ i18n }) {
|
||||
approvalDescriptionHelpers.setValue('');
|
||||
timeoutMinutesHelpers.setValue(0);
|
||||
timeoutSecondsHelpers.setValue(0);
|
||||
convergenceFieldHelpers.setValue('any');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -129,61 +145,107 @@ function NodeTypeStep({ i18n }) {
|
||||
onUpdateNodeResource={nodeResourceHelpers.setValue}
|
||||
/>
|
||||
)}
|
||||
{nodeTypeField.value === 'workflow_approval_template' && (
|
||||
<Form css="margin-top: 20px;">
|
||||
<FormFullWidthLayout>
|
||||
<FormField
|
||||
name="approvalName"
|
||||
id="approval-name"
|
||||
isRequired
|
||||
validate={required(null, i18n)}
|
||||
validated={isValid ? 'default' : 'error'}
|
||||
label={i18n._(t`Name`)}
|
||||
/>
|
||||
<FormField
|
||||
name="approvalDescription"
|
||||
id="approval-description"
|
||||
label={i18n._(t`Description`)}
|
||||
/>
|
||||
<FormGroup
|
||||
label={i18n._(t`Timeout`)}
|
||||
fieldId="approval-timeout"
|
||||
name="timeout"
|
||||
<Form css="margin-top: 20px;">
|
||||
<FormColumnLayout>
|
||||
{nodeTypeField.value === 'workflow_approval_template' && (
|
||||
<FormFullWidthLayout>
|
||||
<FormField
|
||||
name="approvalName"
|
||||
id="approval-name"
|
||||
isRequired
|
||||
validate={required(null, i18n)}
|
||||
validated={isValid ? 'default' : 'error'}
|
||||
label={i18n._(t`Name`)}
|
||||
/>
|
||||
<FormField
|
||||
name="approvalDescription"
|
||||
id="approval-description"
|
||||
label={i18n._(t`Description`)}
|
||||
/>
|
||||
<FormGroup
|
||||
label={i18n._(t`Timeout`)}
|
||||
fieldId="approval-timeout"
|
||||
name="timeout"
|
||||
>
|
||||
<div css="display: flex;align-items: center;">
|
||||
<TimeoutInput
|
||||
{...timeoutMinutesField}
|
||||
aria-label={i18n._(t`Timeout minutes`)}
|
||||
id="approval-timeout-minutes"
|
||||
min="0"
|
||||
onChange={(value, event) => {
|
||||
timeoutMinutesField.onChange(event);
|
||||
}}
|
||||
step="1"
|
||||
type="number"
|
||||
/>
|
||||
<TimeoutLabel>
|
||||
<Trans>min</Trans>
|
||||
</TimeoutLabel>
|
||||
<TimeoutInput
|
||||
{...timeoutSecondsField}
|
||||
aria-label={i18n._(t`Timeout seconds`)}
|
||||
id="approval-timeout-seconds"
|
||||
min="0"
|
||||
onChange={(value, event) => {
|
||||
timeoutSecondsField.onChange(event);
|
||||
}}
|
||||
step="1"
|
||||
type="number"
|
||||
/>
|
||||
<TimeoutLabel>
|
||||
<Trans>sec</Trans>
|
||||
</TimeoutLabel>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</FormFullWidthLayout>
|
||||
)}
|
||||
<FormGroup
|
||||
fieldId="convergence"
|
||||
label={i18n._(t`Convergence`)}
|
||||
isRequired
|
||||
labelIcon={
|
||||
<Popover
|
||||
content={
|
||||
<>
|
||||
{i18n._(
|
||||
t`Preconditions for running this node when there are multiple parents. Refer to the`
|
||||
)}{' '}
|
||||
<a
|
||||
href="https://docs.ansible.com/ansible-tower/latest/html/userguide/workflow_templates.html#convergence-node"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{i18n._(t`documentation`)}
|
||||
</a>{' '}
|
||||
{i18n._(t`for more info.`)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
variant={SelectVariant.single}
|
||||
isOpen={isConvergenceOpen}
|
||||
selections={convergenceField.value}
|
||||
onToggle={setIsConvergenceOpen}
|
||||
onSelect={(event, selection) => {
|
||||
convergenceFieldHelpers.setValue(selection);
|
||||
setIsConvergenceOpen(false);
|
||||
}}
|
||||
aria-label={i18n._(t`Convergence select`)}
|
||||
id="convergence-select"
|
||||
>
|
||||
<div css="display: flex;align-items: center;">
|
||||
<TimeoutInput
|
||||
{...timeoutMinutesField}
|
||||
aria-label={i18n._(t`Timeout minutes`)}
|
||||
id="approval-timeout-minutes"
|
||||
min="0"
|
||||
onChange={(value, event) => {
|
||||
timeoutMinutesField.onChange(event);
|
||||
}}
|
||||
step="1"
|
||||
type="number"
|
||||
/>
|
||||
<TimeoutLabel>
|
||||
<Trans>min</Trans>
|
||||
</TimeoutLabel>
|
||||
<TimeoutInput
|
||||
{...timeoutSecondsField}
|
||||
aria-label={i18n._(t`Timeout seconds`)}
|
||||
id="approval-timeout-seconds"
|
||||
min="0"
|
||||
onChange={(value, event) => {
|
||||
timeoutSecondsField.onChange(event);
|
||||
}}
|
||||
step="1"
|
||||
type="number"
|
||||
/>
|
||||
<TimeoutLabel>
|
||||
<Trans>sec</Trans>
|
||||
</TimeoutLabel>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</FormFullWidthLayout>
|
||||
</Form>
|
||||
)}
|
||||
<SelectOption key="any" value="any">
|
||||
{i18n._(t`Any`)}
|
||||
</SelectOption>
|
||||
<SelectOption key="all" value="all">
|
||||
{i18n._(t`All`)}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</FormColumnLayout>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -177,6 +177,7 @@ describe('NodeTypeStep', () => {
|
||||
approvalDescription: '',
|
||||
timeoutMinutes: 0,
|
||||
timeoutSeconds: 0,
|
||||
convergence: 'any',
|
||||
}}
|
||||
>
|
||||
<NodeTypeStep />
|
||||
|
||||
@ -86,5 +86,6 @@ function getInitialValues() {
|
||||
timeoutMinutes: 0,
|
||||
timeoutSeconds: 0,
|
||||
nodeType: 'job_template',
|
||||
convergence: 'any',
|
||||
};
|
||||
}
|
||||
|
||||
@ -282,6 +282,7 @@ describe('NodeViewModal', () => {
|
||||
description: '',
|
||||
type: 'workflow_approval_template',
|
||||
timeout: 0,
|
||||
all_parents_must_converge: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -39,6 +39,11 @@ const getNodeToEditDefaultValues = (
|
||||
const initialValues = {
|
||||
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
||||
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
||||
convergence:
|
||||
nodeToEdit?.all_parents_must_converge ||
|
||||
nodeToEdit?.originalNodeObject?.all_parents_must_converge
|
||||
? 'all'
|
||||
: 'any',
|
||||
};
|
||||
|
||||
if (
|
||||
@ -228,7 +233,6 @@ export default function useWorkflowNodeSteps(
|
||||
useEffect(() => {
|
||||
if (launchConfig && surveyConfig && isReady) {
|
||||
let initialValues = {};
|
||||
|
||||
if (
|
||||
nodeToEdit &&
|
||||
nodeToEdit?.fullUnifiedJobTemplate &&
|
||||
@ -264,10 +268,15 @@ export default function useWorkflowNodeSteps(
|
||||
);
|
||||
}
|
||||
|
||||
if (initialValues.convergence === 'all') {
|
||||
formikValues.convergence = 'all';
|
||||
}
|
||||
|
||||
resetForm({
|
||||
errors,
|
||||
values: {
|
||||
...initialValues,
|
||||
convergence: formikValues.convergence,
|
||||
nodeResource: formikValues.nodeResource,
|
||||
nodeType: formikValues.nodeType,
|
||||
linkType: formikValues.linkType,
|
||||
|
||||
@ -369,27 +369,24 @@ function Visualizer({ template, i18n }) {
|
||||
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,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
WorkflowJobTemplatesAPI.createNode(template.id, {
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
}).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(
|
||||
@ -397,6 +394,7 @@ function Visualizer({ template, i18n }) {
|
||||
...node.promptValues,
|
||||
inventory: node.promptValues?.inventory?.id || null,
|
||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
}).then(({ data }) => {
|
||||
node.originalNodeObject = data;
|
||||
originalLinkMap[node.id] = {
|
||||
@ -427,27 +425,47 @@ function Visualizer({ template, i18n }) {
|
||||
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(
|
||||
nodeRequests.push(
|
||||
WorkflowJobTemplateNodesAPI.replace(
|
||||
node.originalNodeObject.id,
|
||||
{
|
||||
name: node.fullUnifiedJobTemplate.name,
|
||||
description: node.fullUnifiedJobTemplate.description,
|
||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
}
|
||||
)
|
||||
).then(({ data }) => {
|
||||
node.originalNodeObject = data;
|
||||
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 {
|
||||
nodeRequests.push(
|
||||
WorkflowJobTemplateNodesAPI.replace(
|
||||
node.originalNodeObject.id,
|
||||
{
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
}
|
||||
).then(({ data }) => {
|
||||
node.originalNodeObject = data;
|
||||
approvalTemplateRequests.push(
|
||||
WorkflowJobTemplateNodesAPI.createApprovalTemplate(
|
||||
node.originalNodeObject.id,
|
||||
{
|
||||
name: node.fullUnifiedJobTemplate.name,
|
||||
description: node.fullUnifiedJobTemplate.description,
|
||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -456,6 +474,7 @@ function Visualizer({ template, i18n }) {
|
||||
...node.promptValues,
|
||||
inventory: node.promptValues?.inventory?.id || null,
|
||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||
all_parents_must_converge: node.all_parents_must_converge,
|
||||
}).then(() => {
|
||||
const {
|
||||
added: addedCredentials,
|
||||
|
||||
@ -419,6 +419,7 @@ describe('Visualizer', () => {
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
// TODO: figure out why this test is failing, the scenario passes in the ui
|
||||
test('Error shown when saving fails due to approval template edit error', async () => {
|
||||
workflowReducer.mockImplementation(state => {
|
||||
const newState = {
|
||||
@ -459,6 +460,17 @@ describe('Visualizer', () => {
|
||||
results: [],
|
||||
},
|
||||
});
|
||||
WorkflowJobTemplateNodesAPI.replace.mockResolvedValue({
|
||||
data: {
|
||||
id: 9000,
|
||||
summary_fields: {
|
||||
unified_job_template: {
|
||||
unified_job_type: 'workflow_approval',
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
WorkflowApprovalTemplatesAPI.update.mockRejectedValue(new Error());
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
@ -475,6 +487,7 @@ describe('Visualizer', () => {
|
||||
wrapper.find('Button#visualizer-save').simulate('click');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(WorkflowJobTemplateNodesAPI.replace).toHaveBeenCalledTimes(1);
|
||||
expect(WorkflowApprovalTemplatesAPI.update).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
|
||||
|
||||
@ -44,6 +44,12 @@ const NodeResourceName = styled.p`
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const ConvergenceLabel = styled.p`
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
`;
|
||||
|
||||
NodeResourceName.displayName = 'NodeResourceName';
|
||||
|
||||
function VisualizerNode({
|
||||
@ -244,6 +250,38 @@ function VisualizerNode({
|
||||
node.id
|
||||
].y - nodePositions[1].y})`}
|
||||
>
|
||||
{(node.all_parents_must_converge ||
|
||||
node?.originalNodeObject?.all_parents_must_converge) && (
|
||||
<>
|
||||
<rect
|
||||
fill={
|
||||
hovering && addingLink && !node.isInvalidLinkTarget
|
||||
? '#007ABC'
|
||||
: '#93969A'
|
||||
}
|
||||
height={wfConstants.nodeH / 4}
|
||||
rx={2}
|
||||
ry={2}
|
||||
x={wfConstants.nodeW / 2 - wfConstants.nodeW / 10}
|
||||
y={-wfConstants.nodeH / 4 + 2}
|
||||
stroke={
|
||||
hovering && addingLink && !node.isInvalidLinkTarget
|
||||
? '#007ABC'
|
||||
: '#93969A'
|
||||
}
|
||||
strokeWidth="2px"
|
||||
width={wfConstants.nodeW / 5}
|
||||
/>
|
||||
<foreignObject
|
||||
height={wfConstants.nodeH / 4}
|
||||
width={wfConstants.nodeW / 5}
|
||||
x={wfConstants.nodeW / 2 - wfConstants.nodeW / 10 + 7}
|
||||
y={-wfConstants.nodeH / 4 - 1}
|
||||
>
|
||||
<ConvergenceLabel>{i18n._(t`ALL`)}</ConvergenceLabel>
|
||||
</foreignObject>
|
||||
</>
|
||||
)}
|
||||
<rect
|
||||
fill="#FFFFFF"
|
||||
height={wfConstants.nodeH}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user