Split record crud actions (#8930)

Having a global record crud action adds complex logic.
We decided to split those actions. I only kept a common folder / module
in backend.

⚠️ this may break existing workflows if these were using previous
actions!
This commit is contained in:
Thomas Trompette
2024-12-06 16:58:57 +01:00
committed by GitHub
parent 229a93e41a
commit e1a0259154
26 changed files with 341 additions and 590 deletions

View File

@ -76,8 +76,9 @@ export const WorkflowDiagramStepNodeBase = ({
</StyledStepNodeLabelIconContainer> </StyledStepNodeLabelIconContainer>
); );
} }
case 'RECORD_CRUD.UPDATE': case 'CREATE_RECORD':
case 'RECORD_CRUD.CREATE': { case 'UPDATE_RECORD':
case 'DELETE_RECORD': {
return ( return (
<StyledStepNodeLabelIconContainer> <StyledStepNodeLabelIconContainer>
<IconAddressBook <IconAddressBook
@ -88,10 +89,6 @@ export const WorkflowDiagramStepNodeBase = ({
</StyledStepNodeLabelIconContainer> </StyledStepNodeLabelIconContainer>
); );
} }
case 'RECORD_CRUD.DELETE': {
return null;
}
} }
} }
} }

View File

@ -7,13 +7,10 @@ import {
} from '@/workflow/types/Workflow'; } from '@/workflow/types/Workflow';
import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { WorkflowEditActionFormRecordCreate } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordCreate'; import { WorkflowEditActionFormCreateRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormCreateRecord';
import { WorkflowEditActionFormRecordDelete } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordDelete'; import { WorkflowEditActionFormDeleteRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormDeleteRecord';
import { WorkflowEditActionFormRecordUpdate } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate';
import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-actions/components/WorkflowEditActionFormSendEmail'; import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-actions/components/WorkflowEditActionFormSendEmail';
import { isWorkflowRecordCreateAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordCreateAction'; import { WorkflowEditActionFormUpdateRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormUpdateRecord';
import { isWorkflowRecordDeleteAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordDeleteAction';
import { isWorkflowRecordUpdateAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordUpdateAction';
import { lazy, Suspense } from 'react'; import { lazy, Suspense } from 'react';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader'; import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
@ -107,42 +104,35 @@ export const WorkflowStepDetail = ({
/> />
); );
} }
case 'RECORD_CRUD': { case 'CREATE_RECORD': {
if (isWorkflowRecordCreateAction(stepDefinition.definition)) { return (
return ( <WorkflowEditActionFormCreateRecord
<WorkflowEditActionFormRecordCreate action={stepDefinition.definition}
action={stepDefinition.definition} actionOptions={props}
actionOptions={props} />
/> );
); }
}
if (isWorkflowRecordUpdateAction(stepDefinition.definition)) { case 'UPDATE_RECORD': {
return ( return (
<WorkflowEditActionFormRecordUpdate <WorkflowEditActionFormUpdateRecord
action={stepDefinition.definition} action={stepDefinition.definition}
actionOptions={props} actionOptions={props}
/> />
); );
} }
if (isWorkflowRecordDeleteAction(stepDefinition.definition)) { case 'DELETE_RECORD': {
return ( return (
<WorkflowEditActionFormRecordDelete <WorkflowEditActionFormDeleteRecord
action={stepDefinition.definition} action={stepDefinition.definition}
actionOptions={props} actionOptions={props}
/> />
); );
}
return null;
} }
} }
return assertUnreachable( return null;
stepDefinition.definition,
`Expected the step to have an handler; ${JSON.stringify(stepDefinition)}`,
);
} }
} }

View File

@ -22,17 +22,17 @@ export const ACTIONS: Array<{
}, },
{ {
label: 'Create Record', label: 'Create Record',
type: 'RECORD_CRUD.CREATE', type: 'CREATE_RECORD',
icon: IconAddressBook, icon: IconAddressBook,
}, },
{ {
label: 'Update Record', label: 'Update Record',
type: 'RECORD_CRUD.UPDATE', type: 'UPDATE_RECORD',
icon: IconAddressBook, icon: IconAddressBook,
}, },
{ {
label: 'Delete Record', label: 'Delete Record',
type: 'RECORD_CRUD.DELETE', type: 'DELETE_RECORD',
icon: IconAddressBook, icon: IconAddressBook,
}, },
]; ];

View File

@ -32,34 +32,26 @@ export type WorkflowSendEmailActionSettings = BaseWorkflowActionSettings & {
type ObjectRecord = Record<string, any>; type ObjectRecord = Record<string, any>;
export type WorkflowCreateRecordActionInput = { export type WorkflowCreateRecordActionSettings = BaseWorkflowActionSettings & {
type: 'CREATE'; input: {
objectName: string; objectName: string;
objectRecord: ObjectRecord; objectRecord: ObjectRecord;
};
}; };
export type WorkflowUpdateRecordActionInput = { export type WorkflowUpdateRecordActionSettings = BaseWorkflowActionSettings & {
type: 'UPDATE'; input: {
objectName: string; objectName: string;
objectRecord: ObjectRecord; objectRecord: ObjectRecord;
objectRecordId: string; objectRecordId: string;
};
}; };
export type WorkflowDeleteRecordActionInput = { export type WorkflowDeleteRecordActionSettings = BaseWorkflowActionSettings & {
type: 'DELETE'; input: {
objectName: string; objectName: string;
objectRecordId: string; objectRecordId: string;
}; };
export type WorkflowRecordCRUDActionInput =
| WorkflowCreateRecordActionInput
| WorkflowUpdateRecordActionInput
| WorkflowDeleteRecordActionInput;
export type WorkflowRecordCRUDType = WorkflowRecordCRUDActionInput['type'];
export type WorkflowRecordCRUDActionSettings = BaseWorkflowActionSettings & {
input: WorkflowRecordCRUDActionInput;
}; };
type BaseWorkflowAction = { type BaseWorkflowAction = {
@ -78,35 +70,33 @@ export type WorkflowSendEmailAction = BaseWorkflowAction & {
settings: WorkflowSendEmailActionSettings; settings: WorkflowSendEmailActionSettings;
}; };
export type WorkflowRecordCRUDAction = BaseWorkflowAction & { export type WorkflowCreateRecordAction = BaseWorkflowAction & {
type: 'RECORD_CRUD'; type: 'CREATE_RECORD';
settings: WorkflowRecordCRUDActionSettings; settings: WorkflowCreateRecordActionSettings;
}; };
export type WorkflowRecordCreateAction = WorkflowRecordCRUDAction & { export type WorkflowUpdateRecordAction = BaseWorkflowAction & {
settings: { input: { type: 'CREATE' } }; type: 'UPDATE_RECORD';
settings: WorkflowUpdateRecordActionSettings;
}; };
export type WorkflowRecordUpdateAction = WorkflowRecordCRUDAction & { export type WorkflowDeleteRecordAction = BaseWorkflowAction & {
settings: { input: { type: 'UPDATE' } }; type: 'DELETE_RECORD';
}; settings: WorkflowDeleteRecordActionSettings;
export type WorkflowRecordDeleteAction = WorkflowRecordCRUDAction & {
settings: { input: { type: 'DELETE' } };
}; };
export type WorkflowAction = export type WorkflowAction =
| WorkflowCodeAction | WorkflowCodeAction
| WorkflowSendEmailAction | WorkflowSendEmailAction
| WorkflowRecordCRUDAction; | WorkflowCreateRecordAction
| WorkflowUpdateRecordAction
| WorkflowDeleteRecordAction;
export type WorkflowActionType = WorkflowAction['type'];
export type WorkflowStep = WorkflowAction; export type WorkflowStep = WorkflowAction;
export type WorkflowActionType = export type WorkflowStepType = WorkflowStep['type'];
| Exclude<WorkflowAction['type'], WorkflowRecordCRUDAction['type']>
| `${WorkflowRecordCRUDAction['type']}.${WorkflowRecordCRUDType}`;
export type WorkflowStepType = WorkflowActionType;
type BaseTrigger = { type BaseTrigger = {
name?: string; name?: string;

View File

@ -1,9 +1,5 @@
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
import { import { WorkflowStep, WorkflowTrigger } from '@/workflow/types/Workflow';
WorkflowActionType,
WorkflowStep,
WorkflowTrigger,
} from '@/workflow/types/Workflow';
import { import {
WorkflowDiagram, WorkflowDiagram,
WorkflowDiagramEdge, WorkflowDiagramEdge,
@ -35,25 +31,12 @@ export const generateWorkflowDiagram = ({
) => { ) => {
const nodeId = step.id; const nodeId = step.id;
let nodeActionType: WorkflowActionType;
if (step.type === 'RECORD_CRUD') {
nodeActionType = `RECORD_CRUD.${step.settings.input.type}`;
} else {
nodeActionType = step.type;
}
let nodeLabel = step.name;
if (step.type === 'RECORD_CRUD') {
// FIXME: use activeObjectMetadataItems to get labelSingular
nodeLabel = `${capitalize(step.settings.input.type.toLowerCase())} ${capitalize(step.settings.input.objectName)}`;
}
nodes.push({ nodes.push({
id: nodeId, id: nodeId,
data: { data: {
nodeType: 'action', nodeType: 'action',
actionType: nodeActionType, actionType: step.type,
name: isDefined(step.name) ? step.name : nodeLabel, name: step.name,
}, },
position: { position: {
x: xPos, x: xPos,

View File

@ -5,7 +5,7 @@ import { FormFieldInput } from '@/object-record/record-field/components/FormFiel
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase'; import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
import { WorkflowRecordCreateAction } from '@/workflow/types/Workflow'; import { WorkflowCreateRecordAction } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
@ -18,15 +18,15 @@ import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
type WorkflowEditActionFormRecordCreateProps = { type WorkflowEditActionFormCreateRecordProps = {
action: WorkflowRecordCreateAction; action: WorkflowCreateRecordAction;
actionOptions: actionOptions:
| { | {
readonly: true; readonly: true;
} }
| { | {
readonly?: false; readonly?: false;
onActionUpdate: (action: WorkflowRecordCreateAction) => void; onActionUpdate: (action: WorkflowCreateRecordAction) => void;
}; };
}; };
@ -35,10 +35,10 @@ type CreateRecordFormData = {
[field: string]: unknown; [field: string]: unknown;
}; };
export const WorkflowEditActionFormRecordCreate = ({ export const WorkflowEditActionFormCreateRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionFormRecordCreateProps) => { }: WorkflowEditActionFormCreateRecordProps) => {
const theme = useTheme(); const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -118,7 +118,6 @@ export const WorkflowEditActionFormRecordCreate = ({
settings: { settings: {
...action.settings, ...action.settings,
input: { input: {
type: 'CREATE',
objectName: updatedObjectName, objectName: updatedObjectName,
objectRecord: updatedOtherFields, objectRecord: updatedOtherFields,
}, },

View File

@ -2,7 +2,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase'; import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker'; import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker';
import { WorkflowRecordDeleteAction } from '@/workflow/types/Workflow'; import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
@ -15,15 +15,15 @@ import {
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
type WorkflowEditActionFormRecordDeleteProps = { type WorkflowEditActionFormDeleteRecordProps = {
action: WorkflowRecordDeleteAction; action: WorkflowDeleteRecordAction;
actionOptions: actionOptions:
| { | {
readonly: true; readonly: true;
} }
| { | {
readonly?: false; readonly?: false;
onActionUpdate: (action: WorkflowRecordDeleteAction) => void; onActionUpdate: (action: WorkflowDeleteRecordAction) => void;
}; };
}; };
@ -32,10 +32,10 @@ type DeleteRecordFormData = {
objectRecordId: string; objectRecordId: string;
}; };
export const WorkflowEditActionFormRecordDelete = ({ export const WorkflowEditActionFormDeleteRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionFormRecordDeleteProps) => { }: WorkflowEditActionFormDeleteRecordProps) => {
const theme = useTheme(); const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -100,7 +100,6 @@ export const WorkflowEditActionFormRecordDelete = ({
settings: { settings: {
...action.settings, ...action.settings,
input: { input: {
type: 'DELETE',
objectName: updatedObjectName, objectName: updatedObjectName,
objectRecordId: updatedObjectRecordId ?? '', objectRecordId: updatedObjectRecordId ?? '',
}, },

View File

@ -2,7 +2,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase'; import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker'; import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker';
import { WorkflowRecordUpdateAction } from '@/workflow/types/Workflow'; import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
@ -15,15 +15,15 @@ import {
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
type WorkflowEditActionFormRecordUpdateProps = { type WorkflowEditActionFormUpdateRecordProps = {
action: WorkflowRecordUpdateAction; action: WorkflowUpdateRecordAction;
actionOptions: actionOptions:
| { | {
readonly: true; readonly: true;
} }
| { | {
readonly?: false; readonly?: false;
onActionUpdate: (action: WorkflowRecordUpdateAction) => void; onActionUpdate: (action: WorkflowUpdateRecordAction) => void;
}; };
}; };
@ -33,10 +33,10 @@ type UpdateRecordFormData = {
[field: string]: unknown; [field: string]: unknown;
}; };
export const WorkflowEditActionFormRecordUpdate = ({ export const WorkflowEditActionFormUpdateRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionFormRecordUpdateProps) => { }: WorkflowEditActionFormUpdateRecordProps) => {
const theme = useTheme(); const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -104,7 +104,6 @@ export const WorkflowEditActionFormRecordUpdate = ({
settings: { settings: {
...action.settings, ...action.settings,
input: { input: {
type: 'UPDATE',
objectName: updatedObjectName, objectName: updatedObjectName,
objectRecordId: updatedObjectRecordId ?? '', objectRecordId: updatedObjectRecordId ?? '',
objectRecord: updatedOtherFields, objectRecord: updatedOtherFields,

View File

@ -1,81 +0,0 @@
import {
WorkflowCodeAction,
WorkflowRecordCRUDAction,
} from '@/workflow/types/Workflow';
import { isWorkflowRecordCreateAction } from '../isWorkflowRecordCreateAction';
describe('isWorkflowRecordCreateAction', () => {
it('returns false when providing an action that is not Record Create', () => {
const action: WorkflowCodeAction = {
type: 'CODE',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: {
value: false,
},
retryOnFailure: {
value: false,
},
},
input: {
serverlessFunctionId: '',
serverlessFunctionVersion: '',
serverlessFunctionInput: {},
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordCreateAction(action)).toBe(false);
});
it('returns false for Record Update', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'UPDATE',
objectName: '',
objectRecord: {},
objectRecordId: '',
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordCreateAction(action)).toBe(false);
});
it('returns true for Record Create', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'CREATE',
objectName: '',
objectRecord: {},
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordCreateAction(action)).toBe(true);
});
});

View File

@ -1,81 +0,0 @@
import {
WorkflowCodeAction,
WorkflowRecordCRUDAction,
} from '@/workflow/types/Workflow';
import { isWorkflowRecordDeleteAction } from '../isWorkflowRecordDeleteAction';
describe('isWorkflowRecordDeleteAction', () => {
it('returns false when providing an action that is not Record Delete', () => {
const action: WorkflowCodeAction = {
type: 'CODE',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: {
value: false,
},
retryOnFailure: {
value: false,
},
},
input: {
serverlessFunctionId: '',
serverlessFunctionVersion: '',
serverlessFunctionInput: {},
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordDeleteAction(action)).toBe(false);
});
it('returns false for Record Update', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'UPDATE',
objectName: '',
objectRecord: {},
objectRecordId: '',
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordDeleteAction(action)).toBe(false);
});
it('returns true for Record Delete', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'DELETE',
objectName: '',
objectRecordId: '',
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordDeleteAction(action)).toBe(true);
});
});

View File

@ -1,81 +0,0 @@
import {
WorkflowCodeAction,
WorkflowRecordCRUDAction,
} from '@/workflow/types/Workflow';
import { isWorkflowRecordUpdateAction } from '../isWorkflowRecordUpdateAction';
describe('isWorkflowRecordUpdateAction', () => {
it('returns false when providing an action that is not Record Create', () => {
const action: WorkflowCodeAction = {
type: 'CODE',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: {
value: false,
},
retryOnFailure: {
value: false,
},
},
input: {
serverlessFunctionId: '',
serverlessFunctionVersion: '',
serverlessFunctionInput: {},
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordUpdateAction(action)).toBe(false);
});
it('returns true for Record Update', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'UPDATE',
objectName: '',
objectRecord: {},
objectRecordId: '',
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordUpdateAction(action)).toBe(true);
});
it('returns false for Record Create', () => {
const action: WorkflowRecordCRUDAction = {
type: 'RECORD_CRUD',
id: '',
name: '',
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
type: 'CREATE',
objectName: '',
objectRecord: {},
},
outputSchema: {},
},
valid: true,
};
expect(isWorkflowRecordUpdateAction(action)).toBe(false);
});
});

View File

@ -1,12 +0,0 @@
import {
WorkflowAction,
WorkflowRecordCreateAction,
} from '@/workflow/types/Workflow';
export const isWorkflowRecordCreateAction = (
action: WorkflowAction,
): action is WorkflowRecordCreateAction => {
return (
action.type === 'RECORD_CRUD' && action.settings.input.type === 'CREATE'
);
};

View File

@ -1,12 +0,0 @@
import {
WorkflowAction,
WorkflowRecordDeleteAction,
} from '@/workflow/types/Workflow';
export const isWorkflowRecordDeleteAction = (
action: WorkflowAction,
): action is WorkflowRecordDeleteAction => {
return (
action.type === 'RECORD_CRUD' && action.settings.input.type === 'DELETE'
);
};

View File

@ -1,12 +0,0 @@
import {
WorkflowAction,
WorkflowRecordUpdateAction,
} from '@/workflow/types/Workflow';
export const isWorkflowRecordUpdateAction = (
action: WorkflowAction,
): action is WorkflowRecordUpdateAction => {
return (
action.type === 'RECORD_CRUD' && action.settings.input.type === 'UPDATE'
);
};

View File

@ -18,7 +18,6 @@ import {
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception'; } from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowBuilderWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-builder.workspace-service'; import { WorkflowBuilderWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-builder.workspace-service';
import { WorkflowRecordCRUDType } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type'; import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import { import {
WorkflowAction, WorkflowAction,
@ -126,7 +125,7 @@ export class WorkflowVersionStepWorkspaceService {
}, },
}; };
} }
case `${WorkflowActionType.RECORD_CRUD}.${WorkflowRecordCRUDType.CREATE}`: { case WorkflowActionType.CREATE_RECORD: {
const activeObjectMetadataItem = const activeObjectMetadataItem =
await this.objectMetadataRepository.findOne({ await this.objectMetadataRepository.findOne({
where: { workspaceId, isActive: true, isSystem: false }, where: { workspaceId, isActive: true, isSystem: false },
@ -135,19 +134,18 @@ export class WorkflowVersionStepWorkspaceService {
return { return {
id: newStepId, id: newStepId,
name: 'Create Record', name: 'Create Record',
type: WorkflowActionType.RECORD_CRUD, type: WorkflowActionType.CREATE_RECORD,
valid: false, valid: false,
settings: { settings: {
...BASE_STEP_DEFINITION, ...BASE_STEP_DEFINITION,
input: { input: {
type: WorkflowRecordCRUDType.CREATE,
objectName: activeObjectMetadataItem?.nameSingular || '', objectName: activeObjectMetadataItem?.nameSingular || '',
objectRecord: {}, objectRecord: {},
}, },
}, },
}; };
} }
case `${WorkflowActionType.RECORD_CRUD}.${WorkflowRecordCRUDType.UPDATE}`: { case WorkflowActionType.UPDATE_RECORD: {
const activeObjectMetadataItem = const activeObjectMetadataItem =
await this.objectMetadataRepository.findOne({ await this.objectMetadataRepository.findOne({
where: { workspaceId, isActive: true, isSystem: false }, where: { workspaceId, isActive: true, isSystem: false },
@ -156,12 +154,11 @@ export class WorkflowVersionStepWorkspaceService {
return { return {
id: newStepId, id: newStepId,
name: 'Update Record', name: 'Update Record',
type: WorkflowActionType.RECORD_CRUD, type: WorkflowActionType.UPDATE_RECORD,
valid: false, valid: false,
settings: { settings: {
...BASE_STEP_DEFINITION, ...BASE_STEP_DEFINITION,
input: { input: {
type: WorkflowRecordCRUDType.UPDATE,
objectName: activeObjectMetadataItem?.nameSingular || '', objectName: activeObjectMetadataItem?.nameSingular || '',
objectRecord: {}, objectRecord: {},
objectRecordId: '', objectRecordId: '',
@ -169,7 +166,7 @@ export class WorkflowVersionStepWorkspaceService {
}, },
}; };
} }
case `${WorkflowActionType.RECORD_CRUD}.${WorkflowRecordCRUDType.DELETE}`: { case WorkflowActionType.DELETE_RECORD: {
const activeObjectMetadataItem = const activeObjectMetadataItem =
await this.objectMetadataRepository.findOne({ await this.objectMetadataRepository.findOne({
where: { workspaceId, isActive: true, isSystem: false }, where: { workspaceId, isActive: true, isSystem: false },
@ -178,12 +175,11 @@ export class WorkflowVersionStepWorkspaceService {
return { return {
id: newStepId, id: newStepId,
name: 'Delete Record', name: 'Delete Record',
type: WorkflowActionType.RECORD_CRUD, type: WorkflowActionType.DELETE_RECORD,
valid: false, valid: false,
settings: { settings: {
...BASE_STEP_DEFINITION, ...BASE_STEP_DEFINITION,
input: { input: {
type: WorkflowRecordCRUDType.DELETE,
objectName: activeObjectMetadataItem?.nameSingular || '', objectName: activeObjectMetadataItem?.nameSingular || '',
objectRecordId: '', objectRecordId: '',
}, },

View File

@ -20,7 +20,6 @@ import {
} from 'src/modules/workflow/workflow-builder/types/output-schema.type'; } from 'src/modules/workflow/workflow-builder/types/output-schema.type';
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record'; import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record';
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event'; import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event';
import { WorkflowRecordCRUDType } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { import {
WorkflowAction, WorkflowAction,
WorkflowActionType, WorkflowActionType,
@ -85,10 +84,17 @@ export class WorkflowBuilderWorkspaceService {
codeIntrospectionService: this.codeIntrospectionService, codeIntrospectionService: this.codeIntrospectionService,
}); });
} }
case WorkflowActionType.RECORD_CRUD: case WorkflowActionType.CREATE_RECORD:
return this.computeRecordCrudOutputSchema({ case WorkflowActionType.UPDATE_RECORD:
case WorkflowActionType.DELETE_RECORD:
return this.computeRecordOutputSchema({
objectType: step.settings.input.objectName,
workspaceId,
objectMetadataRepository: this.objectMetadataRepository,
});
case WorkflowActionType.FIND_RECORDS:
return this.computeFindRecordsOutputSchema({
objectType: step.settings.input.objectName, objectType: step.settings.input.objectName,
operationType: step.settings.input.type,
workspaceId, workspaceId,
objectMetadataRepository: this.objectMetadataRepository, objectMetadataRepository: this.objectMetadataRepository,
}); });
@ -130,14 +136,12 @@ export class WorkflowBuilderWorkspaceService {
); );
} }
private async computeRecordCrudOutputSchema({ private async computeFindRecordsOutputSchema({
objectType, objectType,
operationType,
workspaceId, workspaceId,
objectMetadataRepository, objectMetadataRepository,
}: { }: {
objectType: string; objectType: string;
operationType: string;
workspaceId: string; workspaceId: string;
objectMetadataRepository: Repository<ObjectMetadataEntity>; objectMetadataRepository: Repository<ObjectMetadataEntity>;
}): Promise<OutputSchema> { }): Promise<OutputSchema> {
@ -147,24 +151,20 @@ export class WorkflowBuilderWorkspaceService {
objectMetadataRepository, objectMetadataRepository,
}); });
if (operationType === WorkflowRecordCRUDType.READ) { return {
return { first: {
first: { isLeaf: false,
isLeaf: false, icon: 'IconAlpha',
icon: 'IconAlpha', value: recordOutputSchema,
value: recordOutputSchema, },
}, last: { isLeaf: false, icon: 'IconOmega', value: recordOutputSchema },
last: { isLeaf: false, icon: 'IconOmega', value: recordOutputSchema }, totalCount: {
totalCount: { isLeaf: true,
isLeaf: true, icon: 'IconSum',
icon: 'IconSum', type: 'number',
type: 'number', value: generateFakeValue('number'),
value: generateFakeValue('number'), },
}, };
};
}
return recordOutputSchema;
} }
private async computeRecordOutputSchema({ private async computeRecordOutputSchema({

View File

@ -8,7 +8,10 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action'; import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action';
import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action'; import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action';
import { RecordCRUDWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action';
import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action';
import { FindRecordsWorflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@Injectable() @Injectable()
@ -16,7 +19,10 @@ export class WorkflowActionFactory {
constructor( constructor(
private readonly codeWorkflowAction: CodeWorkflowAction, private readonly codeWorkflowAction: CodeWorkflowAction,
private readonly sendEmailWorkflowAction: SendEmailWorkflowAction, private readonly sendEmailWorkflowAction: SendEmailWorkflowAction,
private readonly recordCRUDWorkflowAction: RecordCRUDWorkflowAction, private readonly createRecordWorkflowAction: CreateRecordWorkflowAction,
private readonly updateRecordWorkflowAction: UpdateRecordWorkflowAction,
private readonly deleteRecordWorkflowAction: DeleteRecordWorkflowAction,
private readonly findRecordsWorflowAction: FindRecordsWorflowAction,
) {} ) {}
get(stepType: WorkflowActionType): WorkflowAction { get(stepType: WorkflowActionType): WorkflowAction {
@ -25,8 +31,14 @@ export class WorkflowActionFactory {
return this.codeWorkflowAction; return this.codeWorkflowAction;
case WorkflowActionType.SEND_EMAIL: case WorkflowActionType.SEND_EMAIL:
return this.sendEmailWorkflowAction; return this.sendEmailWorkflowAction;
case WorkflowActionType.RECORD_CRUD: case WorkflowActionType.CREATE_RECORD:
return this.recordCRUDWorkflowAction; return this.createRecordWorkflowAction;
case WorkflowActionType.UPDATE_RECORD:
return this.updateRecordWorkflowAction;
case WorkflowActionType.DELETE_RECORD:
return this.deleteRecordWorkflowAction;
case WorkflowActionType.FIND_RECORDS:
return this.findRecordsWorflowAction;
default: default:
throw new WorkflowStepExecutorException( throw new WorkflowStepExecutorException(
`Workflow step executor not found for step type '${stepType}'`, `Workflow step executor not found for step type '${stepType}'`,

View File

@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-result.type';
@Injectable()
export class CreateRecordWorkflowAction implements WorkflowAction {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
async execute(
workflowActionInput: WorkflowCreateRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.create(
workflowActionInput.objectRecord,
);
await repository.save(objectRecord);
return {
result: objectRecord,
};
}
}

View File

@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-result.type';
@Injectable()
export class DeleteRecordWorkflowAction implements WorkflowAction {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
async execute(
workflowActionInput: WorkflowDeleteRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
});
if (!objectRecord) {
throw new RecordCRUDActionException(
`Failed to delete: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
);
}
await repository.update(workflowActionInput.objectRecordId, {
deletedAt: new Date(),
});
return {
result: objectRecord,
};
}
}

View File

@ -24,18 +24,11 @@ import {
RecordCRUDActionException, RecordCRUDActionException,
RecordCRUDActionExceptionCode, RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception'; } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { import { WorkflowFindRecordsActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
WorkflowCreateRecordActionInput,
WorkflowDeleteRecordActionInput,
WorkflowReadRecordActionInput,
WorkflowRecordCRUDActionInput,
WorkflowRecordCRUDType,
WorkflowUpdateRecordActionInput,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-result.type'; import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-result.type';
@Injectable() @Injectable()
export class RecordCRUDWorkflowAction implements WorkflowAction { export class FindRecordsWorflowAction implements WorkflowAction {
constructor( constructor(
private readonly twentyORMManager: TwentyORMManager, private readonly twentyORMManager: TwentyORMManager,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
@ -43,106 +36,7 @@ export class RecordCRUDWorkflowAction implements WorkflowAction {
) {} ) {}
async execute( async execute(
workflowActionInput: WorkflowRecordCRUDActionInput, workflowActionInput: WorkflowFindRecordsActionInput,
): Promise<WorkflowActionResult> {
switch (workflowActionInput.type) {
case WorkflowRecordCRUDType.CREATE:
return this.createRecord(workflowActionInput);
case WorkflowRecordCRUDType.DELETE:
return this.deleteRecord(workflowActionInput);
case WorkflowRecordCRUDType.UPDATE:
return this.updateRecord(workflowActionInput);
case WorkflowRecordCRUDType.READ:
return this.findRecords(workflowActionInput);
default:
throw new RecordCRUDActionException(
`Unknown record operation type`,
RecordCRUDActionExceptionCode.INVALID_REQUEST,
);
}
}
private async createRecord(
workflowActionInput: WorkflowCreateRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.create(
workflowActionInput.objectRecord,
);
await repository.save(objectRecord);
return {
result: objectRecord,
};
}
private async updateRecord(
workflowActionInput: WorkflowUpdateRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
});
if (!objectRecord) {
throw new RecordCRUDActionException(
`Failed to update: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
);
}
await repository.update(workflowActionInput.objectRecordId, {
...workflowActionInput.objectRecord,
});
return {
result: {
...objectRecord,
...workflowActionInput.objectRecord,
},
};
}
private async deleteRecord(
workflowActionInput: WorkflowDeleteRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
});
if (!objectRecord) {
throw new RecordCRUDActionException(
`Failed to delete: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
);
}
await repository.update(workflowActionInput.objectRecordId, {
deletedAt: new Date(),
});
return {
result: objectRecord,
};
}
private async findRecords(
workflowActionInput: WorkflowReadRecordActionInput,
): Promise<WorkflowActionResult> { ): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository( const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName, workflowActionInput.objectName,
@ -221,7 +115,7 @@ export class RecordCRUDWorkflowAction implements WorkflowAction {
} }
private async getObjectRecords<T extends ObjectLiteral>( private async getObjectRecords<T extends ObjectLiteral>(
workflowActionInput: WorkflowReadRecordActionInput, workflowActionInput: WorkflowFindRecordsActionInput,
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps, objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps, objectMetadataMaps: ObjectMetadataMaps,
repository: WorkspaceRepository<T>, repository: WorkspaceRepository<T>,
@ -261,7 +155,7 @@ export class RecordCRUDWorkflowAction implements WorkflowAction {
} }
private async getTotalCount( private async getTotalCount(
workflowActionInput: WorkflowReadRecordActionInput, workflowActionInput: WorkflowFindRecordsActionInput,
repository: WorkspaceRepository<Entity>, repository: WorkspaceRepository<Entity>,
graphqlQueryParser: GraphqlQueryParser, graphqlQueryParser: GraphqlQueryParser,
) { ) {

View File

@ -2,11 +2,25 @@ import { Module } from '@nestjs/common';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { RecordCRUDWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action';
import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action';
import { FindRecordsWorflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
@Module({ @Module({
imports: [WorkspaceCacheStorageModule], imports: [WorkspaceCacheStorageModule],
providers: [RecordCRUDWorkflowAction, ScopedWorkspaceContextFactory], providers: [
exports: [RecordCRUDWorkflowAction], ScopedWorkspaceContextFactory,
CreateRecordWorkflowAction,
UpdateRecordWorkflowAction,
DeleteRecordWorkflowAction,
FindRecordsWorflowAction,
],
exports: [
CreateRecordWorkflowAction,
UpdateRecordWorkflowAction,
DeleteRecordWorkflowAction,
FindRecordsWorflowAction,
],
}) })
export class RecordCRUDActionModule {} export class RecordCRUDActionModule {}

View File

@ -5,42 +5,25 @@ import {
type ObjectRecord = Record<string, any>; type ObjectRecord = Record<string, any>;
export enum WorkflowRecordCRUDType {
CREATE = 'CREATE',
UPDATE = 'UPDATE',
DELETE = 'DELETE',
READ = 'READ',
}
export type WorkflowCreateRecordActionInput = { export type WorkflowCreateRecordActionInput = {
type: WorkflowRecordCRUDType.CREATE;
objectName: string; objectName: string;
objectRecord: ObjectRecord; objectRecord: ObjectRecord;
}; };
export type WorkflowUpdateRecordActionInput = { export type WorkflowUpdateRecordActionInput = {
type: WorkflowRecordCRUDType.UPDATE;
objectName: string; objectName: string;
objectRecord: ObjectRecord; objectRecord: ObjectRecord;
objectRecordId: string; objectRecordId: string;
}; };
export type WorkflowDeleteRecordActionInput = { export type WorkflowDeleteRecordActionInput = {
type: WorkflowRecordCRUDType.DELETE;
objectName: string; objectName: string;
objectRecordId: string; objectRecordId: string;
}; };
export type WorkflowReadRecordActionInput = { export type WorkflowFindRecordsActionInput = {
type: WorkflowRecordCRUDType.READ;
objectName: string; objectName: string;
filter?: Partial<ObjectRecordFilter>; filter?: Partial<ObjectRecordFilter>;
orderBy?: Partial<ObjectRecordOrderBy>; orderBy?: Partial<ObjectRecordOrderBy>;
limit?: number; limit?: number;
}; };
export type WorkflowRecordCRUDActionInput =
| WorkflowCreateRecordActionInput
| WorkflowUpdateRecordActionInput
| WorkflowDeleteRecordActionInput
| WorkflowReadRecordActionInput;

View File

@ -1,6 +1,23 @@
import { WorkflowRecordCRUDActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type'; import {
WorkflowCreateRecordActionInput,
WorkflowDeleteRecordActionInput,
WorkflowFindRecordsActionInput,
WorkflowUpdateRecordActionInput,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type'; import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
export type WorkflowRecordCRUDActionSettings = BaseWorkflowActionSettings & { export type WorkflowCreateRecordActionSettings = BaseWorkflowActionSettings & {
input: WorkflowRecordCRUDActionInput; input: WorkflowCreateRecordActionInput;
};
export type WorkflowUpdateRecordActionSettings = BaseWorkflowActionSettings & {
input: WorkflowUpdateRecordActionInput;
};
export type WorkflowDeleteRecordActionSettings = BaseWorkflowActionSettings & {
input: WorkflowDeleteRecordActionInput;
};
export type WorkflowFindRecordsActionSettings = BaseWorkflowActionSettings & {
input: WorkflowFindRecordsActionInput;
}; };

View File

@ -0,0 +1,48 @@
import { Injectable } from '@nestjs/common';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { WorkflowUpdateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-result.type';
@Injectable()
export class UpdateRecordWorkflowAction implements WorkflowAction {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
async execute(
workflowActionInput: WorkflowUpdateRecordActionInput,
): Promise<WorkflowActionResult> {
const repository = await this.twentyORMManager.getRepository(
workflowActionInput.objectName,
);
const objectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
});
if (!objectRecord) {
throw new RecordCRUDActionException(
`Failed to update: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
);
}
await repository.update(workflowActionInput.objectRecordId, {
...workflowActionInput.objectRecord,
});
return {
result: {
...objectRecord,
...workflowActionInput.objectRecord,
},
};
}
}

View File

@ -1,7 +1,12 @@
import { OutputSchema } from 'src/modules/workflow/workflow-builder/types/output-schema.type';
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type'; import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type';
import { WorkflowRecordCRUDActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type'; import {
import { OutputSchema } from 'src/modules/workflow/workflow-builder/types/output-schema.type'; WorkflowCreateRecordActionSettings,
WorkflowDeleteRecordActionSettings,
WorkflowFindRecordsActionSettings,
WorkflowUpdateRecordActionSettings,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type';
export type BaseWorkflowActionSettings = { export type BaseWorkflowActionSettings = {
input: object; input: object;
@ -19,4 +24,7 @@ export type BaseWorkflowActionSettings = {
export type WorkflowActionSettings = export type WorkflowActionSettings =
| WorkflowSendEmailActionSettings | WorkflowSendEmailActionSettings
| WorkflowCodeActionSettings | WorkflowCodeActionSettings
| WorkflowRecordCRUDActionSettings; | WorkflowCreateRecordActionSettings
| WorkflowUpdateRecordActionSettings
| WorkflowDeleteRecordActionSettings
| WorkflowFindRecordsActionSettings;

View File

@ -1,12 +1,20 @@
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type'; import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type';
import { WorkflowRecordCRUDActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type'; import {
WorkflowCreateRecordActionSettings,
WorkflowDeleteRecordActionSettings,
WorkflowFindRecordsActionSettings,
WorkflowUpdateRecordActionSettings,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type';
import { WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type'; import { WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
export enum WorkflowActionType { export enum WorkflowActionType {
CODE = 'CODE', CODE = 'CODE',
SEND_EMAIL = 'SEND_EMAIL', SEND_EMAIL = 'SEND_EMAIL',
RECORD_CRUD = 'RECORD_CRUD', CREATE_RECORD = 'CREATE_RECORD',
UPDATE_RECORD = 'UPDATE_RECORD',
DELETE_RECORD = 'DELETE_RECORD',
FIND_RECORDS = 'FIND_RECORDS',
} }
type BaseWorkflowAction = { type BaseWorkflowAction = {
@ -27,12 +35,30 @@ export type WorkflowSendEmailAction = BaseWorkflowAction & {
settings: WorkflowSendEmailActionSettings; settings: WorkflowSendEmailActionSettings;
}; };
export type WorkflowRecordCRUDAction = BaseWorkflowAction & { export type WorkflowCreateRecordAction = BaseWorkflowAction & {
type: WorkflowActionType.RECORD_CRUD; type: WorkflowActionType.CREATE_RECORD;
settings: WorkflowRecordCRUDActionSettings; settings: WorkflowCreateRecordActionSettings;
};
export type WorkflowUpdateRecordAction = BaseWorkflowAction & {
type: WorkflowActionType.UPDATE_RECORD;
settings: WorkflowUpdateRecordActionSettings;
};
export type WorkflowDeleteRecordAction = BaseWorkflowAction & {
type: WorkflowActionType.DELETE_RECORD;
settings: WorkflowDeleteRecordActionSettings;
};
export type WorkflowFindRecordsAction = BaseWorkflowAction & {
type: WorkflowActionType.FIND_RECORDS;
settings: WorkflowFindRecordsActionSettings;
}; };
export type WorkflowAction = export type WorkflowAction =
| WorkflowCodeAction | WorkflowCodeAction
| WorkflowSendEmailAction | WorkflowSendEmailAction
| WorkflowRecordCRUDAction; | WorkflowCreateRecordAction
| WorkflowUpdateRecordAction
| WorkflowDeleteRecordAction
| WorkflowFindRecordsAction;