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 {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.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 {
|
import {
|
||||||
WorkflowAction,
|
WorkflowAction,
|
||||||
WorkflowActionType,
|
WorkflowActionType,
|
||||||
@ -15,6 +12,7 @@ import {
|
|||||||
WorkflowTriggerExceptionCode,
|
WorkflowTriggerExceptionCode,
|
||||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
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(
|
export function assertVersionCanBeActivated(
|
||||||
workflowVersion: WorkflowVersionWorkspaceEntity,
|
workflowVersion: WorkflowVersionWorkspaceEntity,
|
||||||
@ -212,36 +210,3 @@ function assertStepIsValid(step: WorkflowAction) {
|
|||||||
break;
|
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