mirror of
https://github.com/ansible/awx.git
synced 2026-05-11 11:27:36 -02:30
add convergence to workflows
This commit is contained in:
@@ -183,6 +183,7 @@ function createNode(state, node) {
|
|||||||
fullUnifiedJobTemplate: node.nodeResource,
|
fullUnifiedJobTemplate: node.nodeResource,
|
||||||
isInvalidLinkTarget: false,
|
isInvalidLinkTarget: false,
|
||||||
promptValues: node.promptValues,
|
promptValues: node.promptValues,
|
||||||
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensures that root nodes appear to always run
|
// Ensures that root nodes appear to always run
|
||||||
@@ -657,10 +658,19 @@ function updateLink(state, linkType) {
|
|||||||
|
|
||||||
function updateNode(state, editedNode) {
|
function updateNode(state, editedNode) {
|
||||||
const { nodeToEdit, nodes } = state;
|
const { nodeToEdit, nodes } = state;
|
||||||
const { nodeResource, launchConfig, promptValues } = editedNode;
|
const {
|
||||||
|
nodeResource,
|
||||||
|
launchConfig,
|
||||||
|
promptValues,
|
||||||
|
all_parents_must_converge,
|
||||||
|
} = editedNode;
|
||||||
const newNodes = [...nodes];
|
const newNodes = [...nodes];
|
||||||
|
|
||||||
const matchingNode = newNodes.find(node => node.id === nodeToEdit.id);
|
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.fullUnifiedJobTemplate = nodeResource;
|
||||||
matchingNode.isEdited = true;
|
matchingNode.isEdited = true;
|
||||||
matchingNode.launchConfig = launchConfig;
|
matchingNode.launchConfig = launchConfig;
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ const NodeDefaultLabel = styled.p`
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ConvergenceLabel = styled.p`
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
`;
|
||||||
|
|
||||||
Elapsed.displayName = 'Elapsed';
|
Elapsed.displayName = 'Elapsed';
|
||||||
|
|
||||||
function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
|
function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
|
||||||
@@ -100,6 +105,30 @@ function WorkflowOutputNode({ i18n, mouseEnter, mouseLeave, node }) {
|
|||||||
onMouseEnter={mouseEnter}
|
onMouseEnter={mouseEnter}
|
||||||
onMouseLeave={mouseLeave}
|
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
|
<rect
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
height={wfConstants.nodeH}
|
height={wfConstants.nodeH}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function NodeAddModal({ i18n }) {
|
|||||||
timeoutMinutes,
|
timeoutMinutes,
|
||||||
timeoutSeconds,
|
timeoutSeconds,
|
||||||
linkType,
|
linkType,
|
||||||
|
convergence,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
@@ -33,8 +34,11 @@ function NodeAddModal({ i18n }) {
|
|||||||
|
|
||||||
const node = {
|
const node = {
|
||||||
linkType,
|
linkType,
|
||||||
|
all_parents_must_converge: convergence === 'all',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
delete values.convergence;
|
||||||
|
|
||||||
delete values.linkType;
|
delete values.linkType;
|
||||||
|
|
||||||
if (values.nodeType === 'workflow_approval_template') {
|
if (values.nodeType === 'workflow_approval_template') {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ describe('NodeAddModal', () => {
|
|||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
node: {
|
node: {
|
||||||
|
all_parents_must_converge: false,
|
||||||
linkType: 'success',
|
linkType: 'success',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
id: 448,
|
id: 448,
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ function NodeEditModal({ i18n }) {
|
|||||||
nodeType,
|
nodeType,
|
||||||
timeoutMinutes,
|
timeoutMinutes,
|
||||||
timeoutSeconds,
|
timeoutSeconds,
|
||||||
|
convergence,
|
||||||
...rest
|
...rest
|
||||||
} = values;
|
} = values;
|
||||||
let node;
|
let node;
|
||||||
if (values.nodeType === 'workflow_approval_template') {
|
if (values.nodeType === 'workflow_approval_template') {
|
||||||
node = {
|
node = {
|
||||||
|
all_parents_must_converge: convergence === 'all',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
description: approvalDescription,
|
description: approvalDescription,
|
||||||
name: approvalName,
|
name: approvalName,
|
||||||
@@ -32,6 +34,7 @@ function NodeEditModal({ i18n }) {
|
|||||||
} else {
|
} else {
|
||||||
node = {
|
node = {
|
||||||
nodeResource,
|
nodeResource,
|
||||||
|
all_parents_must_converge: convergence === 'all',
|
||||||
};
|
};
|
||||||
if (nodeType === 'job_template' || nodeType === 'workflow_job_template') {
|
if (nodeType === 'job_template' || nodeType === 'workflow_job_template') {
|
||||||
node.promptValues = {
|
node.promptValues = {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ describe('NodeEditModal', () => {
|
|||||||
});
|
});
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
node: {
|
node: {
|
||||||
|
all_parents_must_converge: false,
|
||||||
nodeResource: { id: 448, name: 'Test JT', type: 'job_template' },
|
nodeResource: { id: 448, name: 'Test JT', type: 'job_template' },
|
||||||
},
|
},
|
||||||
type: 'UPDATE_NODE',
|
type: 'UPDATE_NODE',
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ function NodeModalForm({
|
|||||||
values.extra_data = extraVars && parseVariableField(extraVars);
|
values.extra_data = extraVars && parseVariableField(extraVars);
|
||||||
delete values.extra_vars;
|
delete values.extra_vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(values, launchConfig);
|
onSave(values, launchConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -357,6 +356,7 @@ const NodeModal = ({ onSave, i18n, askLinkType, title }) => {
|
|||||||
approvalDescription: '',
|
approvalDescription: '',
|
||||||
timeoutMinutes: 0,
|
timeoutMinutes: 0,
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'success',
|
linkType: 'success',
|
||||||
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
||||||
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'always',
|
linkType: 'always',
|
||||||
nodeType: 'job_template',
|
nodeType: 'job_template',
|
||||||
inventory: { name: 'Foo Inv', id: 1 },
|
inventory: { name: 'Foo Inv', id: 1 },
|
||||||
@@ -345,6 +346,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'failure',
|
linkType: 'failure',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -383,6 +385,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'failure',
|
linkType: 'failure',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -422,6 +425,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'success',
|
linkType: 'success',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -506,6 +510,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
approvalDescription: 'Test Approval Description',
|
approvalDescription: 'Test Approval Description',
|
||||||
approvalName: 'Test Approval',
|
approvalName: 'Test Approval',
|
||||||
linkType: 'always',
|
linkType: 'always',
|
||||||
@@ -605,6 +610,7 @@ describe('NodeModal', () => {
|
|||||||
|
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
approvalDescription: 'Test Approval Description',
|
approvalDescription: 'Test Approval Description',
|
||||||
approvalName: 'Test Approval',
|
approvalName: 'Test Approval',
|
||||||
linkType: 'success',
|
linkType: 'success',
|
||||||
@@ -668,6 +674,7 @@ describe('NodeModal', () => {
|
|||||||
});
|
});
|
||||||
expect(onSave).toBeCalledWith(
|
expect(onSave).toBeCalledWith(
|
||||||
{
|
{
|
||||||
|
convergence: 'any',
|
||||||
linkType: 'success',
|
linkType: 'success',
|
||||||
nodeResource: {
|
nodeResource: {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import 'styled-components/macro';
|
import 'styled-components/macro';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t, Trans } from '@lingui/macro';
|
import { t, Trans } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useField } from 'formik';
|
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 { 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 AnsibleSelect from '../../../../../../components/AnsibleSelect';
|
||||||
import InventorySourcesList from './InventorySourcesList';
|
import InventorySourcesList from './InventorySourcesList';
|
||||||
import JobTemplatesList from './JobTemplatesList';
|
import JobTemplatesList from './JobTemplatesList';
|
||||||
@@ -44,6 +56,9 @@ function NodeTypeStep({ i18n }) {
|
|||||||
const [timeoutSecondsField, , timeoutSecondsHelpers] = useField(
|
const [timeoutSecondsField, , timeoutSecondsHelpers] = useField(
|
||||||
'timeoutSeconds'
|
'timeoutSeconds'
|
||||||
);
|
);
|
||||||
|
const [convergenceField, , convergenceFieldHelpers] = useField('convergence');
|
||||||
|
|
||||||
|
const [isConvergenceOpen, setIsConvergenceOpen] = useState(false);
|
||||||
|
|
||||||
const isValid = !approvalNameMeta.touched || !approvalNameMeta.error;
|
const isValid = !approvalNameMeta.touched || !approvalNameMeta.error;
|
||||||
return (
|
return (
|
||||||
@@ -101,6 +116,7 @@ function NodeTypeStep({ i18n }) {
|
|||||||
approvalDescriptionHelpers.setValue('');
|
approvalDescriptionHelpers.setValue('');
|
||||||
timeoutMinutesHelpers.setValue(0);
|
timeoutMinutesHelpers.setValue(0);
|
||||||
timeoutSecondsHelpers.setValue(0);
|
timeoutSecondsHelpers.setValue(0);
|
||||||
|
convergenceFieldHelpers.setValue('any');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,61 +145,107 @@ function NodeTypeStep({ i18n }) {
|
|||||||
onUpdateNodeResource={nodeResourceHelpers.setValue}
|
onUpdateNodeResource={nodeResourceHelpers.setValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{nodeTypeField.value === 'workflow_approval_template' && (
|
<Form css="margin-top: 20px;">
|
||||||
<Form css="margin-top: 20px;">
|
<FormColumnLayout>
|
||||||
<FormFullWidthLayout>
|
{nodeTypeField.value === 'workflow_approval_template' && (
|
||||||
<FormField
|
<FormFullWidthLayout>
|
||||||
name="approvalName"
|
<FormField
|
||||||
id="approval-name"
|
name="approvalName"
|
||||||
isRequired
|
id="approval-name"
|
||||||
validate={required(null, i18n)}
|
isRequired
|
||||||
validated={isValid ? 'default' : 'error'}
|
validate={required(null, i18n)}
|
||||||
label={i18n._(t`Name`)}
|
validated={isValid ? 'default' : 'error'}
|
||||||
/>
|
label={i18n._(t`Name`)}
|
||||||
<FormField
|
/>
|
||||||
name="approvalDescription"
|
<FormField
|
||||||
id="approval-description"
|
name="approvalDescription"
|
||||||
label={i18n._(t`Description`)}
|
id="approval-description"
|
||||||
/>
|
label={i18n._(t`Description`)}
|
||||||
<FormGroup
|
/>
|
||||||
label={i18n._(t`Timeout`)}
|
<FormGroup
|
||||||
fieldId="approval-timeout"
|
label={i18n._(t`Timeout`)}
|
||||||
name="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;">
|
<SelectOption key="any" value="any">
|
||||||
<TimeoutInput
|
{i18n._(t`Any`)}
|
||||||
{...timeoutMinutesField}
|
</SelectOption>
|
||||||
aria-label={i18n._(t`Timeout minutes`)}
|
<SelectOption key="all" value="all">
|
||||||
id="approval-timeout-minutes"
|
{i18n._(t`All`)}
|
||||||
min="0"
|
</SelectOption>
|
||||||
onChange={(value, event) => {
|
</Select>
|
||||||
timeoutMinutesField.onChange(event);
|
</FormGroup>
|
||||||
}}
|
</FormColumnLayout>
|
||||||
step="1"
|
</Form>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ describe('NodeTypeStep', () => {
|
|||||||
approvalDescription: '',
|
approvalDescription: '',
|
||||||
timeoutMinutes: 0,
|
timeoutMinutes: 0,
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
|
convergence: 'any',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NodeTypeStep />
|
<NodeTypeStep />
|
||||||
|
|||||||
@@ -86,5 +86,6 @@ function getInitialValues() {
|
|||||||
timeoutMinutes: 0,
|
timeoutMinutes: 0,
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
nodeType: 'job_template',
|
nodeType: 'job_template',
|
||||||
|
convergence: 'any',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ describe('NodeViewModal', () => {
|
|||||||
description: '',
|
description: '',
|
||||||
type: 'workflow_approval_template',
|
type: 'workflow_approval_template',
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
|
all_parents_must_converge: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ const getNodeToEditDefaultValues = (
|
|||||||
const initialValues = {
|
const initialValues = {
|
||||||
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null,
|
||||||
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template',
|
||||||
|
convergence:
|
||||||
|
nodeToEdit?.all_parents_must_converge ||
|
||||||
|
nodeToEdit?.originalNodeObject?.all_parents_must_converge
|
||||||
|
? 'all'
|
||||||
|
: 'any',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -228,7 +233,6 @@ export default function useWorkflowNodeSteps(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (launchConfig && surveyConfig && isReady) {
|
if (launchConfig && surveyConfig && isReady) {
|
||||||
let initialValues = {};
|
let initialValues = {};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nodeToEdit &&
|
nodeToEdit &&
|
||||||
nodeToEdit?.fullUnifiedJobTemplate &&
|
nodeToEdit?.fullUnifiedJobTemplate &&
|
||||||
@@ -264,10 +268,15 @@ export default function useWorkflowNodeSteps(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initialValues.convergence === 'all') {
|
||||||
|
formikValues.convergence = 'all';
|
||||||
|
}
|
||||||
|
|
||||||
resetForm({
|
resetForm({
|
||||||
errors,
|
errors,
|
||||||
values: {
|
values: {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
|
convergence: formikValues.convergence,
|
||||||
nodeResource: formikValues.nodeResource,
|
nodeResource: formikValues.nodeResource,
|
||||||
nodeType: formikValues.nodeType,
|
nodeType: formikValues.nodeType,
|
||||||
linkType: formikValues.linkType,
|
linkType: formikValues.linkType,
|
||||||
|
|||||||
@@ -369,27 +369,24 @@ function Visualizer({ template, i18n }) {
|
|||||||
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
|
node.fullUnifiedJobTemplate.type === 'workflow_approval_template'
|
||||||
) {
|
) {
|
||||||
nodeRequests.push(
|
nodeRequests.push(
|
||||||
WorkflowJobTemplatesAPI.createNode(template.id, {}).then(
|
WorkflowJobTemplatesAPI.createNode(template.id, {
|
||||||
({ data }) => {
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
node.originalNodeObject = data;
|
}).then(({ data }) => {
|
||||||
originalLinkMap[node.id] = {
|
node.originalNodeObject = data;
|
||||||
id: data.id,
|
originalLinkMap[node.id] = {
|
||||||
success_nodes: [],
|
id: data.id,
|
||||||
failure_nodes: [],
|
success_nodes: [],
|
||||||
always_nodes: [],
|
failure_nodes: [],
|
||||||
};
|
always_nodes: [],
|
||||||
approvalTemplateRequests.push(
|
};
|
||||||
WorkflowJobTemplateNodesAPI.createApprovalTemplate(
|
approvalTemplateRequests.push(
|
||||||
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,
|
})
|
||||||
}
|
);
|
||||||
)
|
})
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
nodeRequests.push(
|
nodeRequests.push(
|
||||||
@@ -397,6 +394,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,
|
||||||
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
}).then(({ data }) => {
|
}).then(({ data }) => {
|
||||||
node.originalNodeObject = data;
|
node.originalNodeObject = data;
|
||||||
originalLinkMap[node.id] = {
|
originalLinkMap[node.id] = {
|
||||||
@@ -427,27 +425,47 @@ function Visualizer({ template, i18n }) {
|
|||||||
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(
|
nodeRequests.push(
|
||||||
WorkflowApprovalTemplatesAPI.update(
|
WorkflowJobTemplateNodesAPI.replace(
|
||||||
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,
|
node.originalNodeObject.id,
|
||||||
{
|
{
|
||||||
name: node.fullUnifiedJobTemplate.name,
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
description: node.fullUnifiedJobTemplate.description,
|
|
||||||
timeout: node.fullUnifiedJobTemplate.timeout,
|
|
||||||
}
|
}
|
||||||
)
|
).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 {
|
} else {
|
||||||
@@ -456,6 +474,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,
|
||||||
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
const {
|
const {
|
||||||
added: addedCredentials,
|
added: addedCredentials,
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ describe('Visualizer', () => {
|
|||||||
).toBe(1);
|
).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 () => {
|
test('Error shown when saving fails due to approval template edit error', async () => {
|
||||||
workflowReducer.mockImplementation(state => {
|
workflowReducer.mockImplementation(state => {
|
||||||
const newState = {
|
const newState = {
|
||||||
@@ -459,6 +460,17 @@ describe('Visualizer', () => {
|
|||||||
results: [],
|
results: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
WorkflowJobTemplateNodesAPI.replace.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 9000,
|
||||||
|
summary_fields: {
|
||||||
|
unified_job_template: {
|
||||||
|
unified_job_type: 'workflow_approval',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
WorkflowApprovalTemplatesAPI.update.mockRejectedValue(new Error());
|
WorkflowApprovalTemplatesAPI.update.mockRejectedValue(new Error());
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
@@ -475,6 +487,7 @@ describe('Visualizer', () => {
|
|||||||
wrapper.find('Button#visualizer-save').simulate('click');
|
wrapper.find('Button#visualizer-save').simulate('click');
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
expect(WorkflowJobTemplateNodesAPI.replace).toHaveBeenCalledTimes(1);
|
||||||
expect(WorkflowApprovalTemplatesAPI.update).toHaveBeenCalledTimes(1);
|
expect(WorkflowApprovalTemplatesAPI.update).toHaveBeenCalledTimes(1);
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
|
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ const NodeResourceName = styled.p`
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ConvergenceLabel = styled.p`
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
`;
|
||||||
|
|
||||||
NodeResourceName.displayName = 'NodeResourceName';
|
NodeResourceName.displayName = 'NodeResourceName';
|
||||||
|
|
||||||
function VisualizerNode({
|
function VisualizerNode({
|
||||||
@@ -244,6 +250,38 @@ function VisualizerNode({
|
|||||||
node.id
|
node.id
|
||||||
].y - nodePositions[1].y})`}
|
].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
|
<rect
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
height={wfConstants.nodeH}
|
height={wfConstants.nodeH}
|
||||||
|
|||||||
Reference in New Issue
Block a user