diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx index fc1cd2289..1319673bf 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx @@ -1,4 +1,3 @@ -import { lazy, Suspense } from 'react'; import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/components/WorkflowEditTriggerDatabaseEventForm'; import { WorkflowEditTriggerManualForm } from '@/workflow/components/WorkflowEditTriggerManualForm'; import { @@ -8,11 +7,14 @@ import { } from '@/workflow/types/Workflow'; import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; -import { isWorkflowRecordCreateAction } from '@/workflow/utils/isWorkflowRecordCreateAction'; -import { isWorkflowRecordUpdateAction } from '@/workflow/utils/isWorkflowRecordUpdateAction'; import { WorkflowEditActionFormRecordCreate } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordCreate'; +import { WorkflowEditActionFormRecordDelete } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordDelete'; import { WorkflowEditActionFormRecordUpdate } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate'; import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-actions/components/WorkflowEditActionFormSendEmail'; +import { isWorkflowRecordCreateAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordCreateAction'; +import { isWorkflowRecordDeleteAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordDeleteAction'; +import { isWorkflowRecordUpdateAction } from '@/workflow/workflow-actions/utils/isWorkflowRecordUpdateAction'; +import { lazy, Suspense } from 'react'; import { isDefined } from 'twenty-ui'; import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader'; @@ -124,6 +126,15 @@ export const WorkflowStepDetail = ({ ); } + if (isWorkflowRecordDeleteAction(stepDefinition.definition)) { + return ( + + ); + } + return null; } } diff --git a/packages/twenty-front/src/modules/workflow/constants/Actions.ts b/packages/twenty-front/src/modules/workflow/constants/Actions.ts index e71d71ea2..4f5b2c2ba 100644 --- a/packages/twenty-front/src/modules/workflow/constants/Actions.ts +++ b/packages/twenty-front/src/modules/workflow/constants/Actions.ts @@ -30,4 +30,9 @@ export const ACTIONS: Array<{ type: 'RECORD_CRUD.UPDATE', icon: IconAddressBook, }, + { + label: 'Delete Record', + type: 'RECORD_CRUD.DELETE', + icon: IconAddressBook, + }, ]; diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts deleted file mode 100644 index 8a4e3721f..000000000 --- a/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - WorkflowCodeAction, - WorkflowRecordCRUDAction, -} from '@/workflow/types/Workflow'; -import { isWorkflowRecordCreateAction } from '../isWorkflowRecordCreateAction'; - -it('returns false when providing an action that is not Record Create', () => { - const codeAction: WorkflowCodeAction = { - type: 'CODE', - id: '', - name: '', - settings: { - errorHandlingOptions: { - continueOnFailure: { - value: false, - }, - retryOnFailure: { - value: false, - }, - }, - input: { - serverlessFunctionId: '', - serverlessFunctionVersion: '', - serverlessFunctionInput: {}, - }, - outputSchema: {}, - }, - valid: true, - }; - - expect(isWorkflowRecordCreateAction(codeAction)).toBe(false); -}); - -it('returns false for Record Update', () => { - const codeAction: 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(codeAction)).toBe(false); -}); - -it('returns true for Record Create', () => { - const codeAction: WorkflowRecordCRUDAction = { - type: 'RECORD_CRUD', - id: '', - name: '', - settings: { - errorHandlingOptions: { - continueOnFailure: { value: false }, - retryOnFailure: { value: false }, - }, - input: { - type: 'CREATE', - objectName: '', - objectRecord: {}, - }, - outputSchema: {}, - }, - valid: true, - }; - - expect(isWorkflowRecordCreateAction(codeAction)).toBe(true); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordDelete.tsx b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordDelete.tsx new file mode 100644 index 000000000..3ca50035f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordDelete.tsx @@ -0,0 +1,170 @@ +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { Select, SelectOption } from '@/ui/input/components/Select'; +import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase'; +import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker'; +import { WorkflowRecordDeleteAction } from '@/workflow/types/Workflow'; +import { useTheme } from '@emotion/react'; +import { useEffect, useState } from 'react'; +import { + HorizontalSeparator, + IconAddressBook, + isDefined, + useIcons, +} from 'twenty-ui'; + +import { JsonValue } from 'type-fest'; +import { useDebouncedCallback } from 'use-debounce'; + +type WorkflowEditActionFormRecordDeleteProps = { + action: WorkflowRecordDeleteAction; + actionOptions: + | { + readonly: true; + } + | { + readonly?: false; + onActionUpdate: (action: WorkflowRecordDeleteAction) => void; + }; +}; + +type DeleteRecordFormData = { + objectName: string; + objectRecordId: string; +}; + +export const WorkflowEditActionFormRecordDelete = ({ + action, + actionOptions, +}: WorkflowEditActionFormRecordDeleteProps) => { + const theme = useTheme(); + const { getIcon } = useIcons(); + + const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); + + const availableMetadata: Array> = + activeObjectMetadataItems.map((item) => ({ + Icon: getIcon(item.icon), + label: item.labelPlural, + value: item.nameSingular, + })); + + const [formData, setFormData] = useState({ + objectName: action.settings.input.objectName, + objectRecordId: action.settings.input.objectRecordId, + }); + const isFormDisabled = actionOptions.readonly; + + const handleFieldChange = ( + fieldName: keyof DeleteRecordFormData, + updatedValue: JsonValue, + ) => { + const newFormData: DeleteRecordFormData = { + ...formData, + [fieldName]: updatedValue, + }; + + setFormData(newFormData); + + saveAction(newFormData); + }; + + useEffect(() => { + setFormData({ + objectName: action.settings.input.objectName, + objectRecordId: action.settings.input.objectRecordId, + }); + }, [action.settings.input]); + + const selectedObjectMetadataItemNameSingular = formData.objectName; + + const selectedObjectMetadataItem = activeObjectMetadataItems.find( + (item) => item.nameSingular === selectedObjectMetadataItemNameSingular, + ); + if (!isDefined(selectedObjectMetadataItem)) { + throw new Error('Should have found the metadata item'); + } + + const saveAction = useDebouncedCallback( + async (formData: DeleteRecordFormData) => { + if (actionOptions.readonly === true) { + return; + } + + const { + objectName: updatedObjectName, + objectRecordId: updatedObjectRecordId, + } = formData; + + actionOptions.onActionUpdate({ + ...action, + settings: { + ...action.settings, + input: { + type: 'DELETE', + objectName: updatedObjectName, + objectRecordId: updatedObjectRecordId ?? '', + }, + }, + }); + }, + 1_000, + ); + + useEffect(() => { + return () => { + saveAction.flush(); + }; + }, [saveAction]); + + const headerTitle = isDefined(action.name) ? action.name : `Delete Record`; + + return ( + { + if (actionOptions.readonly === true) { + return; + } + + actionOptions.onActionUpdate({ + ...action, + name: newName, + }); + }} + Icon={IconAddressBook} + iconColor={theme.font.color.tertiary} + initialTitle={headerTitle} + headerType="Action" + > +