From e01d3fd0beb35ab0ddc0214c8a183268a403f59f Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:12:31 +0200 Subject: [PATCH] Do not override value for composite types address and links when entering input (#6502) Closes #6434. We don't want to override the values of the records' address or links as they are composite field and it is costly to loose the data. We will need a more unified behaviour here - maybe introduce a Ctrl+Z option. --------- Co-authored-by: Charles Bochet --- .../constants/FieldsNotOverwrittenAtDraft.ts | 6 ++ .../record-field/hooks/useInitDraftValueV2.ts | 11 +- .../record-field/hooks/useRecordFieldInput.ts | 41 +------ .../utils/computeDraftValueFromString.ts | 23 +++- .../utils/computeEmptyDraftValue.ts | 21 +++- .../record-inline-cell/hooks/useInlineCell.ts | 8 +- .../__tests__/useOpenRecordTableCell.test.tsx | 76 ------------- .../hooks/useOpenRecordTableCell.ts | 101 ------------------ .../pages/object-record/RecordIndexPage.tsx | 2 +- .../integrations/message-queue/jobs.module.ts | 3 + 10 files changed, 61 insertions(+), 231 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/constants/FieldsNotOverwrittenAtDraft.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useOpenRecordTableCell.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts diff --git a/packages/twenty-front/src/modules/object-record/constants/FieldsNotOverwrittenAtDraft.ts b/packages/twenty-front/src/modules/object-record/constants/FieldsNotOverwrittenAtDraft.ts new file mode 100644 index 000000000..902b2f878 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/constants/FieldsNotOverwrittenAtDraft.ts @@ -0,0 +1,6 @@ +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const FIELD_NOT_OVERWRITTEN_AT_DRAFT = [ + FieldMetadataType.Address, + FieldMetadataType.Links, +]; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts index 3e84eccf7..546a143d6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts @@ -1,6 +1,7 @@ import { isUndefined } from '@sniptt/guards'; import { useRecoilCallback } from 'recoil'; +import { FIELD_NOT_OVERWRITTEN_AT_DRAFT } from '@/object-record/constants/FieldsNotOverwrittenAtDraft'; import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; @@ -37,7 +38,10 @@ export const useInitDraftValueV2 = () => { ) .getValue(); - if (isUndefined(value)) { + if ( + isUndefined(value) || + FIELD_NOT_OVERWRITTEN_AT_DRAFT.includes(fieldDefinition.type) + ) { set( getDraftValueSelector(), computeDraftValueFromFieldValue({ @@ -48,7 +52,10 @@ export const useInitDraftValueV2 = () => { } else { set( getDraftValueSelector(), - computeDraftValueFromString({ value, fieldDefinition }), + computeDraftValueFromString({ + value, + fieldDefinition, + }), ); } }, diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordFieldInput.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordFieldInput.ts index 0aefcca7e..0fbb69d7d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordFieldInput.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordFieldInput.ts @@ -1,13 +1,7 @@ -import { useContext } from 'react'; -import { isUndefined } from '@sniptt/guards'; -import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useRecordFieldInputStates } from '@/object-record/record-field/hooks/internal/useRecordFieldInputStates'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; -import { computeDraftValueFromFieldValue } from '@/object-record/record-field/utils/computeDraftValueFromFieldValue'; -import { computeDraftValueFromString } from '@/object-record/record-field/utils/computeDraftValueFromString'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; export const useRecordFieldInput = ( recordFieldInputId?: string, @@ -15,40 +9,8 @@ export const useRecordFieldInput = ( const { scopeId, getDraftValueSelector } = useRecordFieldInputStates(recordFieldInputId); - const { entityId, fieldDefinition } = useContext(FieldContext); - const setDraftValue = useSetRecoilState(getDraftValueSelector()); - const initDraftValue = useRecoilCallback( - ({ set, snapshot }) => - (value?: string) => { - const recordFieldValue = snapshot - .getLoadable( - recordStoreFamilySelector({ - recordId: entityId, - fieldName: fieldDefinition.metadata.fieldName, - }), - ) - .getValue(); - - if (isUndefined(value)) { - set( - getDraftValueSelector(), - computeDraftValueFromFieldValue({ - fieldValue: recordFieldValue, - fieldDefinition, - }), - ); - } else { - set( - getDraftValueSelector(), - computeDraftValueFromString({ value, fieldDefinition }), - ); - } - }, - [entityId, fieldDefinition, getDraftValueSelector], - ); - const isDraftValueEmpty = ( value: FieldInputDraftValue | undefined, ) => { @@ -67,7 +29,6 @@ export const useRecordFieldInput = ( scopeId, setDraftValue, getDraftValueSelector, - initDraftValue, isDraftValueEmpty, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts index dcb00de6f..d41d5fd85 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts @@ -2,12 +2,15 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; +import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; +import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; @@ -19,18 +22,20 @@ type computeDraftValueFromStringParams = { export const computeDraftValueFromString = ({ fieldDefinition, value, -}: computeDraftValueFromStringParams): FieldInputDraftValue => { +}: computeDraftValueFromStringParams): + | FieldInputDraftValue + | undefined => { // Todo: improve typing if ( isFieldUuid(fieldDefinition) || isFieldText(fieldDefinition) || isFieldDateTime(fieldDefinition) || isFieldNumber(fieldDefinition) || - isFieldEmail(fieldDefinition) + isFieldEmail(fieldDefinition) || + isFieldRelation(fieldDefinition) ) { return value as FieldInputDraftValue; } - if (isFieldLink(fieldDefinition)) { return { url: value, label: value } as FieldInputDraftValue; } @@ -49,5 +54,17 @@ export const computeDraftValueFromString = ({ } as FieldInputDraftValue; } + if (isFieldAddress(fieldDefinition)) { + return { + addressStreet1: value, + } as FieldInputDraftValue; + } + + if (isFieldLinks(fieldDefinition)) { + return { + primaryLinkUrl: value, + } as FieldInputDraftValue; + } + throw new Error(`Record field type not supported : ${fieldDefinition.type}}`); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts index b37ec8400..af950cf20 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts @@ -2,11 +2,12 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; -import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; +import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; @@ -33,8 +34,22 @@ export const computeEmptyDraftValue = ({ return '' as FieldInputDraftValue; } - if (isFieldLink(fieldDefinition)) { - return { url: '', label: '' } as FieldInputDraftValue; + if (isFieldLinks(fieldDefinition)) { + return { + primaryLinkUrl: '', + primaryLinkLabel: '', + } as FieldInputDraftValue; + } + + if (isFieldAddress(fieldDefinition)) { + return { + addressStreet1: '', + addressStreet2: '', + addressCity: '', + addressState: '', + addressCountry: '', + addressPostcode: '', + } as FieldInputDraftValue; } if (isFieldCurrency(fieldDefinition)) { diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts index 7ff151b03..7437ac2f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts @@ -2,11 +2,11 @@ import { useContext } from 'react'; import { useRecoilState } from 'recoil'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { isDefined } from '~/utils/isDefined'; +import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2'; import { isInlineCellInEditModeScopedState } from '../states/isInlineCellInEditModeScopedState'; import { InlineCellHotkeyScope } from '../types/InlineCellHotkeyScope'; @@ -26,9 +26,7 @@ export const useInlineCell = () => { goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - const { initDraftValue: initFieldInputDraftValue } = useRecordFieldInput( - `${entityId}-${fieldDefinition?.metadata?.fieldName}`, - ); + const initFieldInputDraftValue = useInitDraftValueV2(); const closeInlineCell = () => { setIsInlineCellInEditMode(false); @@ -38,7 +36,7 @@ export const useInlineCell = () => { const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => { setIsInlineCellInEditMode(true); - initFieldInputDraftValue(); + initFieldInputDraftValue({ entityId, fieldDefinition }); if (isDefined(customEditHotkeyScopeForField)) { setHotkeyScopeAndMemorizePreviousScope( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useOpenRecordTableCell.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useOpenRecordTableCell.test.tsx deleted file mode 100644 index b144300eb..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useOpenRecordTableCell.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { MemoryRouter } from 'react-router-dom'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; - -import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; -import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { - recordTableCell, - recordTableRow, -} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; -import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; -import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; - -const setHotkeyScope = jest.fn(); - -jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({ - useSetHotkeyScope: () => setHotkeyScope, -})); - -const onColumnsChange = jest.fn(); -const scopeId = 'scopeId'; - -const Wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - - - {children} - - - - - - -); - -describe('useOpenRecordTableCell', () => { - it('should work as expected', async () => { - const { result } = renderHook( - () => { - return { ...useOpenRecordTableCell(), ...useDragSelect() }; - }, - { - wrapper: Wrapper, - }, - ); - - expect(result.current.isDragSelectionStartEnabled()).toBe(true); - - act(() => { - result.current.openTableCell(); - }); - - await waitFor(() => { - expect(result.current.isDragSelectionStartEnabled()).toBe(false); - }); - - expect(setHotkeyScope).toHaveBeenCalledWith('cell-edit-mode', undefined); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts deleted file mode 100644 index f5a069436..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useRecoilCallback } from 'recoil'; - -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty'; -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; -import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId'; -import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; -import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus'; -import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; -import { isDefined } from '~/utils/isDefined'; - -import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; -import { TableHotkeyScope } from '../../types/TableHotkeyScope'; - -import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode'; - -export const DEFAULT_CELL_SCOPE: HotkeyScope = { - scope: TableHotkeyScope.CellEditMode, -}; - -export const useOpenRecordTableCell = () => { - const { pathToShowPage, isReadOnly } = useContext(RecordTableRowContext); - - const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); - const setHotkeyScope = useSetHotkeyScope(); - const { setDragSelectionStartEnabled } = useDragSelect(); - - const customCellHotkeyScope = useContext(CellHotkeyScopeContext); - - const navigate = useNavigate(); - const leaveTableFocus = useLeaveTableFocus(); - const { toggleClickOutsideListener } = useClickOutsideListener( - SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID, - ); - - const { columnIndex } = useContext(RecordTableCellContext); - const isFirstColumnCell = columnIndex === 0; - const isEmpty = useIsFieldEmpty(); - - const { entityId, fieldDefinition } = useContext(FieldContext); - - const { initDraftValue: initFieldInputDraftValue } = useRecordFieldInput( - `${entityId}-${fieldDefinition?.metadata?.fieldName}`, - ); - - const openTableCell = useRecoilCallback( - () => (options?: { initialValue?: string }) => { - if (isReadOnly) { - return; - } - - if (isFirstColumnCell && !isEmpty) { - leaveTableFocus(); - navigate(pathToShowPage); - return; - } - - setDragSelectionStartEnabled(false); - setCurrentTableCellInEditMode(); - - initFieldInputDraftValue(options?.initialValue); - toggleClickOutsideListener(false); - - if (isDefined(customCellHotkeyScope)) { - setHotkeyScope( - customCellHotkeyScope.scope, - customCellHotkeyScope.customScopes, - ); - } else { - setHotkeyScope( - DEFAULT_CELL_SCOPE.scope, - DEFAULT_CELL_SCOPE.customScopes, - ); - } - }, - [ - isReadOnly, - isFirstColumnCell, - isEmpty, - setDragSelectionStartEnabled, - setCurrentTableCellInEditMode, - initFieldInputDraftValue, - toggleClickOutsideListener, - customCellHotkeyScope, - leaveTableFocus, - navigate, - pathToShowPage, - setHotkeyScope, - ], - ); - - return { - openTableCell, - }; -}; diff --git a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx index 2e4a0268d..2df55058e 100644 --- a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx @@ -5,7 +5,7 @@ import { v4 } from 'uuid'; import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer'; import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; -import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; +import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode'; import { PageBody } from '@/ui/layout/page/PageBody'; import { PageContainer } from '@/ui/layout/page/PageContainer'; diff --git a/packages/twenty-server/src/engine/integrations/message-queue/jobs.module.ts b/packages/twenty-server/src/engine/integrations/message-queue/jobs.module.ts index 6dc82d5cb..6516455d5 100644 --- a/packages/twenty-server/src/engine/integrations/message-queue/jobs.module.ts +++ b/packages/twenty-server/src/engine/integrations/message-queue/jobs.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; @@ -11,6 +12,7 @@ import { StripeModule } from 'src/engine/core-modules/billing/stripe/stripe.modu import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { UserModule } from 'src/engine/core-modules/user/user.module'; import { HandleWorkspaceMemberDeletedJob } from 'src/engine/core-modules/workspace/handle-workspace-member-deleted.job'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { EmailSenderJob } from 'src/engine/integrations/email/email-sender.job'; import { EmailModule } from 'src/engine/integrations/email/email.module'; @@ -27,6 +29,7 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module'; @Module({ imports: [ + TypeOrmModule.forFeature([Workspace], 'core'), DataSourceModule, ObjectMetadataModule, TypeORMModule,