From b542b438782060f722767f490e14f9872a9c69a9 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 29 Nov 2024 20:33:45 +0100 Subject: [PATCH] Add record picker with variables (#8813) - Add update actions - Create a folder for workflow actions - Add a SingleRecordPicker with variables handler https://github.com/user-attachments/assets/9fd57ce1-1b8d-424a-8aa1-69468d684fa1 --- .../WorkflowDiagramStepNodeBase.tsx | 5 +- .../WorkflowSingleRecordFieldChip.tsx | 62 +++++ .../components/WorkflowSingleRecordPicker.tsx | 219 ++++++++++++++++++ .../components/WorkflowStepDetail.tsx | 17 +- .../src/modules/workflow/constants/Actions.ts | 5 + .../getStepDefaultDefinition.test.ts | 40 +++- .../utils/getStepDefaultDefinition.ts | 61 ++--- .../utils/isWorkflowRecordUpdateAction.ts | 12 + .../WorkflowEditActionFormRecordCreate.tsx | 12 +- .../WorkflowEditActionFormRecordUpdate.tsx | 179 ++++++++++++++ .../WorkflowEditActionFormSendEmail.tsx | 0 ...rkflowEditActionFormServerlessFunction.tsx | 0 12 files changed, 563 insertions(+), 49 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordFieldChip.tsx create mode 100644 packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordPicker.tsx create mode 100644 packages/twenty-front/src/modules/workflow/utils/isWorkflowRecordUpdateAction.ts rename packages/twenty-front/src/modules/workflow/{ => workflow-actions}/components/WorkflowEditActionFormRecordCreate.tsx (94%) create mode 100644 packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate.tsx rename packages/twenty-front/src/modules/workflow/{ => workflow-actions}/components/WorkflowEditActionFormSendEmail.tsx (100%) rename packages/twenty-front/src/modules/workflow/{ => workflow-actions}/components/WorkflowEditActionFormServerlessFunction.tsx (100%) diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx index 03d0921ca..859d22ee4 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx @@ -76,6 +76,7 @@ export const WorkflowDiagramStepNodeBase = ({ ); } + case 'RECORD_CRUD.UPDATE': case 'RECORD_CRUD.CREATE': { return ( @@ -87,8 +88,8 @@ export const WorkflowDiagramStepNodeBase = ({ ); } - case 'RECORD_CRUD.DELETE': - case 'RECORD_CRUD.UPDATE': { + + case 'RECORD_CRUD.DELETE': { return null; } } diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordFieldChip.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordFieldChip.tsx new file mode 100644 index 000000000..b1d1fbf92 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordFieldChip.tsx @@ -0,0 +1,62 @@ +import { RecordChip } from '@/object-record/components/RecordChip'; +import { VariableChip } from '@/object-record/record-field/form-types/components/VariableChip'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { + RecordId, + Variable, +} from '@/workflow/components/WorkflowSingleRecordPicker'; +import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; +import styled from '@emotion/styled'; + +const StyledRecordChip = styled(RecordChip)` + margin: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledPlaceholder = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin: ${({ theme }) => theme.spacing(2)}; +`; + +type WorkflowSingleRecordFieldChipProps = { + draftValue: + | { + type: 'static'; + value: RecordId; + } + | { + type: 'variable'; + value: Variable; + }; + selectedRecord?: ObjectRecord; + objectNameSingular: string; + onRemove: () => void; +}; + +export const WorkflowSingleRecordFieldChip = ({ + draftValue, + selectedRecord, + objectNameSingular, + onRemove, +}: WorkflowSingleRecordFieldChipProps) => { + if ( + !!draftValue && + draftValue.type === 'variable' && + isStandaloneVariableString(draftValue.value) + ) { + return ( + + ); + } + + if (!!draftValue && draftValue.type === 'static' && !!selectedRecord) { + return ( + + ); + } + + return Select a {objectNameSingular}; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordPicker.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordPicker.tsx new file mode 100644 index 000000000..c97167aa6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowSingleRecordPicker.tsx @@ -0,0 +1,219 @@ +import { + IconChevronDown, + IconForbid, + isDefined, + LightIconButton, +} from 'twenty-ui'; + +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { StyledFormFieldInputContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputContainer'; +import { StyledFormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputRowContainer'; +import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect'; +import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecordPicker'; +import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; +import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { InputLabel } from '@/ui/input/components/InputLabel'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; +import { WorkflowSingleRecordFieldChip } from '@/workflow/components/WorkflowSingleRecordFieldChip'; +import SearchVariablesDropdown from '@/workflow/search-variables/components/SearchVariablesDropdown'; +import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useCallback, useState } from 'react'; +import { isValidUuid } from '~/utils/isValidUuid'; + +const StyledFormSelectContainer = styled.div` + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-top-left-radius: ${({ theme }) => theme.border.radius.sm}; + border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm}; + border-right: none; + border-bottom-right-radius: none; + border-top-right-radius: none; + box-sizing: border-box; + display: flex; + overflow: 'hidden'; + width: 100%; + justify-content: space-between; + align-items: center; + padding-right: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledSearchVariablesDropdownContainer = styled.div` + align-items: center; + display: flex; + justify-content: center; + ${({ theme }) => css` + :hover { + background-color: ${theme.background.transparent.light}; + } + `} + ${({ theme }) => css` + background-color: ${theme.background.transparent.lighter}; + border-top-right-radius: ${theme.border.radius.sm}; + border-bottom-right-radius: ${theme.border.radius.sm}; + border: 1px solid ${theme.border.color.medium}; + `} +`; + +export type RecordId = string; +export type Variable = string; + +export type WorkflowSingleRecordPickerProps = { + label?: string; + defaultValue: RecordId | Variable; + onChange: (value: RecordId | Variable) => void; + objectNameSingular: string; +}; + +export const WorkflowSingleRecordPicker = ({ + label, + defaultValue, + objectNameSingular, + onChange, +}: WorkflowSingleRecordPickerProps) => { + const [draftValue, setDraftValue] = useState< + | { + type: 'static'; + value: RecordId; + } + | { + type: 'variable'; + value: Variable; + } + >( + isStandaloneVariableString(defaultValue) + ? { + type: 'variable', + value: defaultValue, + } + : { + type: 'static', + value: defaultValue || '', + }, + ); + + const { record } = useFindOneRecord({ + objectRecordId: + isDefined(defaultValue) && !isStandaloneVariableString(defaultValue) + ? defaultValue + : '', + objectNameSingular, + withSoftDeleted: true, + skip: !isValidUuid(defaultValue), + }); + + const [selectedRecord, setSelectedRecord] = useState< + ObjectRecord | undefined + >(record); + + const dropdownId = `workflow-record-picker-${objectNameSingular}`; + const variablesDropdownId = `workflow-record-picker-${objectNameSingular}-variables`; + + const { closeDropdown } = useDropdown(dropdownId); + + const { setRecordPickerSearchFilter } = useRecordPicker({ + recordPickerInstanceId: dropdownId, + }); + + const handleCloseRelationPickerDropdown = useCallback(() => { + setRecordPickerSearchFilter(''); + }, [setRecordPickerSearchFilter]); + + const handleRecordSelected = ( + selectedEntity: RecordForSelect | null | undefined, + ) => { + setDraftValue({ + type: 'static', + value: selectedEntity?.record?.id ?? '', + }); + setSelectedRecord(selectedEntity?.record); + closeDropdown(); + + onChange?.(selectedEntity?.record?.id ?? ''); + }; + + const handleVariableTagInsert = (variable: string) => { + setDraftValue({ + type: 'variable', + value: variable, + }); + setSelectedRecord(undefined); + closeDropdown(); + + onChange?.(variable); + }; + + const handleUnlinkVariable = () => { + setDraftValue({ + type: 'static', + value: '', + }); + closeDropdown(); + + onChange(''); + }; + + return ( + + {label ? {label} : null} + + + + + + } + dropdownComponents={ + + closeDropdown()} + onRecordSelected={handleRecordSelected} + objectNameSingular={objectNameSingular} + recordPickerInstanceId={dropdownId} + selectedRecordIds={ + draftValue?.value && + !isStandaloneVariableString(draftValue.value) + ? [draftValue.value] + : [] + } + /> + + } + dropdownHotkeyScope={{ + scope: dropdownId, + }} + /> + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx index 00914401b..41fe04c16 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx @@ -1,6 +1,3 @@ -import { WorkflowEditActionFormRecordCreate } from '@/workflow/components/WorkflowEditActionFormRecordCreate'; -import { WorkflowEditActionFormSendEmail } from '@/workflow/components/WorkflowEditActionFormSendEmail'; -import { WorkflowEditActionFormServerlessFunction } from '@/workflow/components/WorkflowEditActionFormServerlessFunction'; import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/components/WorkflowEditTriggerDatabaseEventForm'; import { WorkflowEditTriggerManualForm } from '@/workflow/components/WorkflowEditTriggerManualForm'; import { @@ -11,6 +8,11 @@ import { 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 { WorkflowEditActionFormRecordUpdate } from '@/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate'; +import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-actions/components/WorkflowEditActionFormSendEmail'; +import { WorkflowEditActionFormServerlessFunction } from '@/workflow/workflow-actions/components/WorkflowEditActionFormServerlessFunction'; import { isDefined } from 'twenty-ui'; type WorkflowStepDetailProps = @@ -102,6 +104,15 @@ export const WorkflowStepDetail = ({ ); } + if (isWorkflowRecordUpdateAction(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 c4a9bae48..e71d71ea2 100644 --- a/packages/twenty-front/src/modules/workflow/constants/Actions.ts +++ b/packages/twenty-front/src/modules/workflow/constants/Actions.ts @@ -25,4 +25,9 @@ export const ACTIONS: Array<{ type: 'RECORD_CRUD.CREATE', icon: IconAddressBook, }, + { + label: 'Update Record', + type: 'RECORD_CRUD.UPDATE', + icon: IconAddressBook, + }, ]; diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts index 8d8515e65..cd7b54308 100644 --- a/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts @@ -92,6 +92,37 @@ it('returns a valid definition for RECORD_CRUD.CREATE actions', () => { }); }); +it('returns a valid definition for RECORD_CRUD.UPDATE actions', () => { + expect( + getStepDefaultDefinition({ + type: 'RECORD_CRUD.UPDATE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + id: expect.any(String), + name: 'Update Record', + type: 'RECORD_CRUD', + valid: false, + settings: { + input: { + type: 'UPDATE', + objectName: generatedMockObjectMetadataItems[0].nameSingular, + objectRecord: {}, + objectRecordId: '', + }, + outputSchema: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + }); +}); + it("throws for RECORD_CRUD.DELETE actions as it's not implemented yet", () => { expect(() => { getStepDefaultDefinition({ @@ -101,15 +132,6 @@ it("throws for RECORD_CRUD.DELETE actions as it's not implemented yet", () => { }).toThrow('Not implemented yet'); }); -it("throws for RECORD_CRUD.UPDATE actions as it's not implemented yet", () => { - expect(() => { - getStepDefaultDefinition({ - type: 'RECORD_CRUD.UPDATE', - activeObjectMetadataItems: generatedMockObjectMetadataItems, - }); - }).toThrow('Not implemented yet'); -}); - it('throws when providing an unknown type', () => { expect(() => { getStepDefaultDefinition({ diff --git a/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts b/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts index 20a33f8b9..ddb95c326 100644 --- a/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts +++ b/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts @@ -3,6 +3,18 @@ import { WorkflowStep, WorkflowStepType } from '@/workflow/types/Workflow'; import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; import { v4 } from 'uuid'; +const BASE_DEFAULT_STEP_SETTINGS = { + outputSchema: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, +}; + export const getStepDefaultDefinition = ({ type, activeObjectMetadataItems, @@ -25,15 +37,7 @@ export const getStepDefaultDefinition = ({ serverlessFunctionVersion: '', serverlessFunctionInput: {}, }, - outputSchema: {}, - errorHandlingOptions: { - continueOnFailure: { - value: false, - }, - retryOnFailure: { - value: false, - }, - }, + ...BASE_DEFAULT_STEP_SETTINGS, }, }; } @@ -50,15 +54,7 @@ export const getStepDefaultDefinition = ({ subject: '', body: '', }, - outputSchema: {}, - errorHandlingOptions: { - continueOnFailure: { - value: false, - }, - retryOnFailure: { - value: false, - }, - }, + ...BASE_DEFAULT_STEP_SETTINGS, }, }; } @@ -74,20 +70,27 @@ export const getStepDefaultDefinition = ({ objectName: activeObjectMetadataItems[0].nameSingular, objectRecord: {}, }, - outputSchema: {}, - errorHandlingOptions: { - continueOnFailure: { - value: false, - }, - retryOnFailure: { - value: false, - }, - }, + ...BASE_DEFAULT_STEP_SETTINGS, }, }; } - case 'RECORD_CRUD.DELETE': - case 'RECORD_CRUD.UPDATE': { + case 'RECORD_CRUD.UPDATE': + return { + id: newStepId, + name: 'Update Record', + type: 'RECORD_CRUD', + valid: false, + settings: { + input: { + type: 'UPDATE', + objectName: activeObjectMetadataItems[0].nameSingular, + objectRecordId: '', + objectRecord: {}, + }, + ...BASE_DEFAULT_STEP_SETTINGS, + }, + }; + case 'RECORD_CRUD.DELETE': { throw new Error('Not implemented yet'); } default: { diff --git a/packages/twenty-front/src/modules/workflow/utils/isWorkflowRecordUpdateAction.ts b/packages/twenty-front/src/modules/workflow/utils/isWorkflowRecordUpdateAction.ts new file mode 100644 index 000000000..732ba25a6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/isWorkflowRecordUpdateAction.ts @@ -0,0 +1,12 @@ +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' + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormRecordCreate.tsx b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordCreate.tsx similarity index 94% rename from packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormRecordCreate.tsx rename to packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordCreate.tsx index 7408e2465..3c181c7b7 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormRecordCreate.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordCreate.tsx @@ -28,7 +28,7 @@ type WorkflowEditActionFormRecordCreateProps = { }; }; -type SendEmailFormData = { +type CreateRecordFormData = { objectName: string; [field: string]: unknown; }; @@ -49,17 +49,17 @@ export const WorkflowEditActionFormRecordCreate = ({ value: item.nameSingular, })); - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ objectName: action.settings.input.objectName, ...action.settings.input.objectRecord, }); const isFormDisabled = actionOptions.readonly; const handleFieldChange = ( - fieldName: keyof SendEmailFormData, + fieldName: keyof CreateRecordFormData, updatedValue: JsonValue, ) => { - const newFormData: SendEmailFormData = { + const newFormData: CreateRecordFormData = { ...formData, [fieldName]: updatedValue, }; @@ -93,7 +93,7 @@ export const WorkflowEditActionFormRecordCreate = ({ ); const saveAction = useDebouncedCallback( - async (formData: SendEmailFormData) => { + async (formData: CreateRecordFormData) => { if (actionOptions.readonly === true) { return; } @@ -153,7 +153,7 @@ export const WorkflowEditActionFormRecordCreate = ({ emptyOption={{ label: 'Select an option', value: '' }} options={availableMetadata} onChange={(updatedObjectName) => { - const newFormData: SendEmailFormData = { + const newFormData: CreateRecordFormData = { objectName: updatedObjectName, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate.tsx b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate.tsx new file mode 100644 index 000000000..bb30074cc --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-actions/components/WorkflowEditActionFormRecordUpdate.tsx @@ -0,0 +1,179 @@ +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 { WorkflowRecordUpdateAction } 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 WorkflowEditActionFormRecordUpdateProps = { + action: WorkflowRecordUpdateAction; + actionOptions: + | { + readonly: true; + } + | { + readonly?: false; + onActionUpdate: (action: WorkflowRecordUpdateAction) => void; + }; +}; + +type UpdateRecordFormData = { + objectName: string; + objectRecordId: string; + [field: string]: unknown; +}; + +export const WorkflowEditActionFormRecordUpdate = ({ + action, + actionOptions, +}: WorkflowEditActionFormRecordUpdateProps) => { + 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, + ...action.settings.input.objectRecord, + }); + const isFormDisabled = actionOptions.readonly; + + const handleFieldChange = ( + fieldName: keyof UpdateRecordFormData, + updatedValue: JsonValue, + ) => { + const newFormData: UpdateRecordFormData = { + ...formData, + [fieldName]: updatedValue, + }; + + setFormData(newFormData); + + saveAction(newFormData); + }; + + useEffect(() => { + setFormData({ + objectName: action.settings.input.objectName, + objectRecordId: action.settings.input.objectRecordId, + ...action.settings.input.objectRecord, + }); + }, [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: UpdateRecordFormData) => { + if (actionOptions.readonly === true) { + return; + } + + const { + objectName: updatedObjectName, + objectRecordId: updatedObjectRecordId, + ...updatedOtherFields + } = formData; + + actionOptions.onActionUpdate({ + ...action, + settings: { + ...action.settings, + input: { + type: 'UPDATE', + objectName: updatedObjectName, + objectRecordId: updatedObjectRecordId ?? '', + objectRecord: updatedOtherFields, + }, + }, + }); + }, + 1_000, + ); + + useEffect(() => { + return () => { + saveAction.flush(); + }; + }, [saveAction]); + + const headerTitle = isDefined(action.name) ? action.name : `Update Record`; + + return ( + { + if (actionOptions.readonly === true) { + return; + } + + actionOptions.onActionUpdate({ + ...action, + name: newName, + }); + }} + HeaderIcon={ + + } + headerTitle={headerTitle} + headerType="Action" + > +