Prevent empty form steps (#12560)
https://github.com/user-attachments/assets/b9fd0269-fffa-4027-a634-15a5234980f0 Related to https://github.com/twentyhq/core-team-issues/issues/1091
This commit is contained in:
committed by
GitHub
parent
cb5a895963
commit
6b0517943f
@ -0,0 +1,171 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import { WorkflowTriggerException } from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
import { assertFormStepIsValid } from 'src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util';
|
||||
|
||||
const settings: WorkflowFormActionSettings = {
|
||||
input: [
|
||||
{
|
||||
id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2',
|
||||
name: 'text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Text',
|
||||
},
|
||||
{
|
||||
id: '3cc0d22a-e6e7-4c07-b45b-c4fbb83ba3bf',
|
||||
name: 'record',
|
||||
type: 'RECORD',
|
||||
label: 'Record',
|
||||
settings: {
|
||||
objectName: 'company',
|
||||
},
|
||||
placeholder: '',
|
||||
},
|
||||
],
|
||||
outputSchema: {
|
||||
text: {
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Text',
|
||||
value: 'My text',
|
||||
isLeaf: true,
|
||||
},
|
||||
record: {
|
||||
label: 'Record',
|
||||
value: {
|
||||
fields: {
|
||||
id: {
|
||||
icon: 'Icon123',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: 'Id',
|
||||
value: '123e4567-e89b-12d3-a456-426614174000',
|
||||
isLeaf: true,
|
||||
},
|
||||
},
|
||||
object: {
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
label: 'Company',
|
||||
value: 'A company',
|
||||
isLeaf: true,
|
||||
fieldIdName: 'id',
|
||||
nameSingular: 'company',
|
||||
},
|
||||
_outputSchemaType: 'RECORD',
|
||||
},
|
||||
isLeaf: false,
|
||||
},
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('assertFormStepIsValid', () => {
|
||||
it('should throw an exception when input is not provided', () => {
|
||||
const invalidSettings: WorkflowFormActionSettings = {
|
||||
...settings,
|
||||
// @ts-expect-error Intentionally invalid input
|
||||
input: undefined,
|
||||
};
|
||||
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
'No input provided in form step',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when input array is empty', () => {
|
||||
const invalidSettings: WorkflowFormActionSettings = {
|
||||
...settings,
|
||||
input: [],
|
||||
};
|
||||
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
'Form action must have at least one field',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when field names are not unique', () => {
|
||||
const invalidSettings: WorkflowFormActionSettings = {
|
||||
...settings,
|
||||
input: [
|
||||
{
|
||||
id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2',
|
||||
name: 'text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Text',
|
||||
},
|
||||
{
|
||||
id: '3cc0d22a-e6e7-4c07-b45b-c4fbb83ba3bf',
|
||||
name: 'text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Text Duplicate',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
'Form action fields must have unique names',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when field label is not defined', () => {
|
||||
const invalidSettings: WorkflowFormActionSettings = {
|
||||
...settings,
|
||||
input: [
|
||||
{
|
||||
id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2',
|
||||
name: 'text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
'Form action fields must have a defined label and type',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when field type is not defined', () => {
|
||||
const invalidSettings: WorkflowFormActionSettings = {
|
||||
...settings,
|
||||
input: [
|
||||
{
|
||||
id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2',
|
||||
name: 'text',
|
||||
// @ts-expect-error Intentionally invalid type
|
||||
type: '',
|
||||
label: 'Text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
WorkflowTriggerException,
|
||||
);
|
||||
expect(() => assertFormStepIsValid(invalidSettings)).toThrow(
|
||||
'Form action fields must have a defined label and type',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw an exception for valid form settings', () => {
|
||||
expect(() => assertFormStepIsValid(settings)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import {
|
||||
WorkflowTriggerException,
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
|
||||
export function assertFormStepIsValid(settings: WorkflowFormActionSettings) {
|
||||
if (!settings.input) {
|
||||
throw new WorkflowTriggerException(
|
||||
'No input provided in form step',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.input.length === 0) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Form action must have at least one field',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
// Check all fields have unique and defined names
|
||||
const fieldNames = settings.input.map((fieldMetadata) => fieldMetadata.name);
|
||||
const uniqueFieldNames = new Set(fieldNames);
|
||||
|
||||
if (fieldNames.length !== uniqueFieldNames.size) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Form action fields must have unique names',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
// Check all fields have defined labels and types
|
||||
settings.input.forEach((fieldMetadata) => {
|
||||
if (
|
||||
!isNonEmptyString(fieldMetadata.label) ||
|
||||
!isNonEmptyString(fieldMetadata.type)
|
||||
) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Form action fields must have a defined label and type',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1,11 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
WorkflowVersionStatus,
|
||||
WorkflowVersionWorkspaceEntity,
|
||||
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowActionType,
|
||||
@ -15,6 +12,7 @@ import {
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import { assertFormStepIsValid } from 'src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util';
|
||||
|
||||
export function assertVersionCanBeActivated(
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity,
|
||||
@ -212,36 +210,3 @@ function assertStepIsValid(step: WorkflowAction) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function assertFormStepIsValid(settings: WorkflowFormActionSettings) {
|
||||
if (!settings.input) {
|
||||
throw new WorkflowTriggerException(
|
||||
'No input provided in form step',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||
);
|
||||
}
|
||||
|
||||
// Check all fields have unique and defined names
|
||||
const fieldNames = settings.input.map((fieldMetadata) => fieldMetadata.name);
|
||||
const uniqueFieldNames = new Set(fieldNames);
|
||||
|
||||
if (fieldNames.length !== uniqueFieldNames.size) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Form action fields must have unique names',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
// Check all fields have defined labels and types
|
||||
settings.input.forEach((fieldMetadata) => {
|
||||
if (
|
||||
!isNonEmptyString(fieldMetadata.label) ||
|
||||
!isNonEmptyString(fieldMetadata.type)
|
||||
) {
|
||||
throw new WorkflowTriggerException(
|
||||
'Form action fields must have a defined label and type',
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user