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 <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,6 @@
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const FIELD_NOT_OVERWRITTEN_AT_DRAFT = [
|
||||
FieldMetadataType.Address,
|
||||
FieldMetadataType.Links,
|
||||
];
|
||||
@ -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 = <FieldValue>() => {
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isUndefined(value)) {
|
||||
if (
|
||||
isUndefined(value) ||
|
||||
FIELD_NOT_OVERWRITTEN_AT_DRAFT.includes(fieldDefinition.type)
|
||||
) {
|
||||
set(
|
||||
getDraftValueSelector(),
|
||||
computeDraftValueFromFieldValue<FieldValue>({
|
||||
@ -48,7 +52,10 @@ export const useInitDraftValueV2 = <FieldValue>() => {
|
||||
} else {
|
||||
set(
|
||||
getDraftValueSelector(),
|
||||
computeDraftValueFromString<FieldValue>({ value, fieldDefinition }),
|
||||
computeDraftValueFromString<FieldValue>({
|
||||
value,
|
||||
fieldDefinition,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@ -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 = <FieldValue>(
|
||||
recordFieldInputId?: string,
|
||||
@ -15,40 +9,8 @@ export const useRecordFieldInput = <FieldValue>(
|
||||
const { scopeId, getDraftValueSelector } =
|
||||
useRecordFieldInputStates<FieldValue>(recordFieldInputId);
|
||||
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const setDraftValue = useSetRecoilState(getDraftValueSelector());
|
||||
|
||||
const initDraftValue = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(value?: string) => {
|
||||
const recordFieldValue = snapshot
|
||||
.getLoadable(
|
||||
recordStoreFamilySelector<FieldValue>({
|
||||
recordId: entityId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isUndefined(value)) {
|
||||
set(
|
||||
getDraftValueSelector(),
|
||||
computeDraftValueFromFieldValue<FieldValue>({
|
||||
fieldValue: recordFieldValue,
|
||||
fieldDefinition,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
set(
|
||||
getDraftValueSelector(),
|
||||
computeDraftValueFromString<FieldValue>({ value, fieldDefinition }),
|
||||
);
|
||||
}
|
||||
},
|
||||
[entityId, fieldDefinition, getDraftValueSelector],
|
||||
);
|
||||
|
||||
const isDraftValueEmpty = (
|
||||
value: FieldInputDraftValue<FieldValue> | undefined,
|
||||
) => {
|
||||
@ -67,7 +29,6 @@ export const useRecordFieldInput = <FieldValue>(
|
||||
scopeId,
|
||||
setDraftValue,
|
||||
getDraftValueSelector,
|
||||
initDraftValue,
|
||||
isDraftValueEmpty,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 = <FieldValue>({
|
||||
fieldDefinition,
|
||||
value,
|
||||
}: computeDraftValueFromStringParams): FieldInputDraftValue<FieldValue> => {
|
||||
}: computeDraftValueFromStringParams):
|
||||
| FieldInputDraftValue<FieldValue>
|
||||
| undefined => {
|
||||
// Todo: improve typing
|
||||
if (
|
||||
isFieldUuid(fieldDefinition) ||
|
||||
isFieldText(fieldDefinition) ||
|
||||
isFieldDateTime(fieldDefinition) ||
|
||||
isFieldNumber(fieldDefinition) ||
|
||||
isFieldEmail(fieldDefinition)
|
||||
isFieldEmail(fieldDefinition) ||
|
||||
isFieldRelation(fieldDefinition)
|
||||
) {
|
||||
return value as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldLink(fieldDefinition)) {
|
||||
return { url: value, label: value } as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
@ -49,5 +54,17 @@ export const computeDraftValueFromString = <FieldValue>({
|
||||
} as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldAddress(fieldDefinition)) {
|
||||
return {
|
||||
addressStreet1: value,
|
||||
} as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldLinks(fieldDefinition)) {
|
||||
return {
|
||||
primaryLinkUrl: value,
|
||||
} as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
throw new Error(`Record field type not supported : ${fieldDefinition.type}}`);
|
||||
};
|
||||
|
||||
@ -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 = <FieldValue>({
|
||||
return '' as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldLink(fieldDefinition)) {
|
||||
return { url: '', label: '' } as FieldInputDraftValue<FieldValue>;
|
||||
if (isFieldLinks(fieldDefinition)) {
|
||||
return {
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: '',
|
||||
} as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldAddress(fieldDefinition)) {
|
||||
return {
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
} as FieldInputDraftValue<FieldValue>;
|
||||
}
|
||||
|
||||
if (isFieldCurrency(fieldDefinition)) {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 }) => (
|
||||
<RecoilRoot>
|
||||
<RecordTableScope
|
||||
recordTableScopeId={scopeId}
|
||||
onColumnsChange={onColumnsChange}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
entityId: 'entityId',
|
||||
hotkeyScope: TableHotkeyScope.Table,
|
||||
isLabelIdentifier: false,
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContext.Provider value={recordTableRow}>
|
||||
<RecordTableCellContext.Provider value={recordTableCell}>
|
||||
<MemoryRouter initialEntries={['/one', '/two']} initialIndex={1}>
|
||||
{children}
|
||||
</MemoryRouter>
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableScope>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user