Workflow iterator continues on faillure (#18325)
<img width="450" height="212" alt="Capture d’écran 2026-03-03 à 11 41 54" src="https://github.com/user-attachments/assets/b2c29a48-7dc0-4b16-a085-8f305d21f7ca" /> New status `FAIL_SAFE` added. This status propagates to the following nodes until reaching the iterator, that will start the new iteration. The difference with `SKIP` is that, when the parent nodes have at least one `FAIL_SAFE`, it becomes `FAIL_SAFE` too. While a parent 1 `SKIP` + parent 2 `SUCCESS` => to be executed. I also thought about just going back to the iterator as a break would, but since we have branches, it may lead to inconsistent statuses with parallel updates.
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer';
|
||||
import { FormFieldInputInnerContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputInnerContainer';
|
||||
import { FormFieldInputRowContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputRowContainer';
|
||||
import { InputHint } from '@/ui/input/components/InputHint';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import styled from '@emotion/styled';
|
||||
import { useId } from 'react';
|
||||
import { Toggle } from 'twenty-ui/input';
|
||||
|
||||
type FormBooleanFieldToggleInputProps = {
|
||||
label?: string;
|
||||
description: string;
|
||||
hint?: string;
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const StyledDescription = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const StyledToggleContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const FormBooleanFieldToggleInput = ({
|
||||
label,
|
||||
description,
|
||||
hint,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: FormBooleanFieldToggleInputProps) => {
|
||||
const instanceId = useId();
|
||||
|
||||
return (
|
||||
<FormFieldInputContainer>
|
||||
{label ? <InputLabel>{label}</InputLabel> : null}
|
||||
|
||||
<FormFieldInputRowContainer>
|
||||
<FormFieldInputInnerContainer
|
||||
formFieldInputInstanceId={instanceId}
|
||||
hasRightElement
|
||||
preventFocusStackUpdate
|
||||
>
|
||||
<StyledDescription>{description}</StyledDescription>
|
||||
</FormFieldInputInnerContainer>
|
||||
|
||||
<StyledToggleContainer>
|
||||
<Toggle
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</StyledToggleContainer>
|
||||
</FormFieldInputRowContainer>
|
||||
|
||||
{hint && <InputHint>{hint}</InputHint>}
|
||||
</FormFieldInputContainer>
|
||||
);
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { FormBooleanFieldToggleInput } from '@/object-record/record-field/ui/form-types/components/FormBooleanFieldToggleInput';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
const meta: Meta<typeof FormBooleanFieldToggleInput> = {
|
||||
title: 'UI/Data/Field/Form/Input/FormBooleanFieldToggleInput',
|
||||
component: FormBooleanFieldToggleInput,
|
||||
args: {
|
||||
description: 'Continue on iteration failure',
|
||||
value: false,
|
||||
onChange: fn(),
|
||||
},
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof FormBooleanFieldToggleInput>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('Continue on iteration failure');
|
||||
|
||||
const checkbox = canvas.getByRole('checkbox');
|
||||
|
||||
expect(checkbox).not.toBeChecked();
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
args: {
|
||||
label: 'Settings',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('Settings');
|
||||
await canvas.findByText('Continue on iteration failure');
|
||||
},
|
||||
};
|
||||
|
||||
export const WithHint: Story = {
|
||||
args: {
|
||||
hint: 'If enabled, the workflow will continue to the next iteration even if the current one fails.',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText(
|
||||
'If enabled, the workflow will continue to the next iteration even if the current one fails.',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ToggledOn: Story = {
|
||||
args: {
|
||||
value: true,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const checkbox = canvas.getByRole('checkbox');
|
||||
|
||||
expect(checkbox).toBeChecked();
|
||||
},
|
||||
};
|
||||
|
||||
export const TogglesValue: Story = {
|
||||
args: {
|
||||
value: false,
|
||||
onChange: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const checkbox = canvas.getByRole('checkbox');
|
||||
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(args.onChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user