fix: soft deleted records are read only (#8198)

TODO:
- [ ] It should not be possible to add tasks, notes, files, etc.

Fix for https://github.com/twentyhq/twenty/issues/7172

Note for reviewer:
- With these changes, `deletedAt` is now always included in
`recordGqlFields`.

--- Edit from Charles --
In this PR:
1) As mentionned by @pau-not-paul, we are adding deletedAt to our
recordGqlFields for board and table
2) I'm removing cellReadOnly logic, it is now fully computed using
useIsFieldValueReadonly like it's done in other places, there is no need
to maintain two read only systems
3) refactoring useIsFieldValueReadonly to take all the business logics
into one place together. It's now a function of the 5 factors (isRemote,
isDeleted, objectName, fieldName, etc...). Later it's likely to get back
to a function of Pick<FieldMetadata>, Pick<ObjectMetadata>,
record.isDeleted but we are not there yet

Note: as all cells are listening to the record (for isDeleted), updating
a field will trigger a re-rendering of the row which is not an issue
right now. It will be if we introduce bulk edition. In this case we
would need to use a selector on fields on top of record store

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
pau-not-paul 2024-11-21 14:02:09 +01:00 committed by GitHub
parent 3c5eb539bb
commit ae4fb7d113
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 327 additions and 135 deletions

View File

@ -108,5 +108,6 @@ export const actorFieldDefinition: FieldDefinition<FieldActorMetadata> = {
defaultValue: { source: 'MANUAL', name: '' },
metadata: {
fieldName: 'actor',
objectMetadataNameSingular: 'person',
},
};

View File

@ -1,50 +0,0 @@
import { renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import {
actorFieldDefinition,
phonesFieldDefinition,
} from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
const recordId = 'recordId';
const getWrapper =
(fieldDefinition: FieldDefinition<FieldMetadata>) =>
({ children }: { children: ReactNode }) => (
<FieldContext.Provider
value={{
fieldDefinition,
recordId,
hotkeyScope: 'hotkeyScope',
isLabelIdentifier: false,
}}
>
<RecoilRoot>{children}</RecoilRoot>
</FieldContext.Provider>
);
const ActorWrapper = getWrapper(actorFieldDefinition);
const PhoneWrapper = getWrapper(phonesFieldDefinition);
describe('useIsFieldReadOnly', () => {
it('should return true', () => {
const { result } = renderHook(() => useIsFieldReadOnly(), {
wrapper: ActorWrapper,
});
expect(result.current).toBe(true);
});
it('should return false', () => {
const { result } = renderHook(() => useIsFieldReadOnly(), {
wrapper: PhoneWrapper,
});
expect(result.current).toBe(false);
});
});

View File

@ -0,0 +1,74 @@
import { renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import {
actorFieldDefinition,
phonesFieldDefinition,
} from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
import { JestRecordStoreSetter } from '~/testing/jest/JestRecordStoreSetter';
import { useIsFieldValueReadOnly } from '../useIsFieldValueReadOnly';
const recordId = 'recordId';
const getWrapper =
(fieldDefinition: FieldDefinition<FieldMetadata>, isRecordDeleted: boolean) =>
({ children }: { children: ReactNode }) => {
return (
<RecoilRoot>
<JestObjectMetadataItemSetter>
<JestRecordStoreSetter
records={[
{
id: recordId,
deletedAt: isRecordDeleted ? new Date().toISOString() : null,
__typename: 'standardObject',
} as ObjectRecord,
]}
>
<FieldContext.Provider
value={{
fieldDefinition,
recordId,
hotkeyScope: 'hotkeyScope',
isLabelIdentifier: false,
}}
>
{children}
</FieldContext.Provider>
</JestRecordStoreSetter>
</JestObjectMetadataItemSetter>
</RecoilRoot>
);
};
describe('useIsFieldValueReadOnly', () => {
it('should take fieldDefinition into account', () => {
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
wrapper: getWrapper(phonesFieldDefinition, false),
});
expect(result.current).toBe(false);
const { result: result2 } = renderHook(() => useIsFieldValueReadOnly(), {
wrapper: getWrapper(actorFieldDefinition, false),
});
expect(result2.current).toBe(true);
});
it('should take isRecordDeleted into account', () => {
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
wrapper: getWrapper(phonesFieldDefinition, true),
});
expect(result.current).toBe(true);
});
});

View File

@ -1,18 +0,0 @@
import { useContext } from 'react';
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { FieldContext } from '../contexts/FieldContext';
import { isFieldMetadataReadOnly } from '../utils/isFieldMetadataReadOnly';
export const useIsFieldReadOnly = () => {
const { fieldDefinition } = useContext(FieldContext);
const { metadata } = fieldDefinition;
return (
isFieldActor(fieldDefinition) ||
isFieldRichText(fieldDefinition) ||
isFieldMetadataReadOnly(metadata)
);
};

View File

@ -0,0 +1,30 @@
import { useContext } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useRecoilValue } from 'recoil';
import { FieldContext } from '../contexts/FieldContext';
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
export const useIsFieldValueReadOnly = () => {
const { fieldDefinition, recordId } = useContext(FieldContext);
const { metadata, type } = fieldDefinition;
const recordFromStore = useRecoilValue<ObjectRecord | null>(
recordStoreFamilyState(recordId),
);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: metadata.objectMetadataNameSingular ?? '',
});
return isFieldValueReadOnly({
objectNameSingular: metadata.objectMetadataNameSingular,
fieldName: metadata.fieldName,
fieldType: type,
isObjectRemote: objectMetadataItem.isRemote,
isRecordDeleted: recordFromStore?.deletedAt,
});
};

View File

@ -0,0 +1,113 @@
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
import { FieldMetadataType } from '~/generated/graphql';
describe('isFieldValueReadOnly', () => {
it('should return true if fieldName is noteTargets or taskTargets', () => {
const result = isFieldValueReadOnly({
fieldName: 'noteTargets',
});
expect(result).toBe(true);
const result2 = isFieldValueReadOnly({
fieldName: 'taskTargets',
});
expect(result2).toBe(true);
});
it('should return false if fieldName is not noteTargets or taskTargets', () => {
const result = isFieldValueReadOnly({
fieldName: 'test',
});
expect(result).toBe(false);
});
it('should return true if isObjectRemote is true', () => {
const result = isFieldValueReadOnly({
isObjectRemote: true,
});
expect(result).toBe(true);
});
it('should return false if isObjectRemote is false', () => {
const result = isFieldValueReadOnly({
isObjectRemote: false,
});
expect(result).toBe(false);
});
it('should return true if isRecordDeleted is true', () => {
const result = isFieldValueReadOnly({
isRecordDeleted: true,
});
expect(result).toBe(true);
});
it('should return false if isRecordDeleted is false', () => {
const result = isFieldValueReadOnly({
isRecordDeleted: false,
});
expect(result).toBe(false);
});
it('should return true if objectNameSingular is Workflow and fieldName is not name', () => {
const result = isFieldValueReadOnly({
objectNameSingular: 'workflow',
fieldName: 'test',
});
expect(result).toBe(true);
});
it('should return false if objectNameSingular is Workflow and fieldName is name', () => {
const result = isFieldValueReadOnly({
objectNameSingular: 'Workflow',
fieldName: 'name',
});
expect(result).toBe(false);
});
it('should return true if isWorkflowSubObjectMetadata is true', () => {
const result = isFieldValueReadOnly({
objectNameSingular: 'workflowVersion',
});
expect(result).toBe(true);
});
it('should return true if fieldType is FieldMetadataType.Actor', () => {
const result = isFieldValueReadOnly({
fieldType: FieldMetadataType.Actor,
});
expect(result).toBe(true);
});
it('should return true if fieldType is FieldMetadataType.RichText', () => {
const result = isFieldValueReadOnly({
fieldType: FieldMetadataType.RichText,
});
expect(result).toBe(true);
});
it('should return false if fieldType is not FieldMetadataType.Actor or FieldMetadataType.RichText', () => {
const result = isFieldValueReadOnly({
fieldType: FieldMetadataType.Text,
});
expect(result).toBe(false);
});
it('should return false if none of the conditions are met', () => {
const result = isFieldValueReadOnly({});
expect(result).toBe(false);
});
});

View File

@ -1,19 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
export const isFieldMetadataReadOnly = (fieldMetadata: FieldMetadata) => {
if (
fieldMetadata.fieldName === 'noteTargets' ||
fieldMetadata.fieldName === 'taskTargets'
) {
return true;
}
return (
isWorkflowSubObjectMetadata(fieldMetadata.objectMetadataNameSingular) ||
(fieldMetadata.objectMetadataNameSingular ===
CoreObjectNameSingular.Workflow &&
fieldMetadata.fieldName !== 'name')
);
};

View File

@ -0,0 +1,54 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type isFieldValueReadOnlyParams = {
objectNameSingular?: string;
fieldName?: string;
fieldType?: FieldMetadataType;
isObjectRemote?: boolean;
isRecordDeleted?: boolean;
};
export const isFieldValueReadOnly = ({
objectNameSingular,
fieldName,
fieldType,
isObjectRemote = false,
isRecordDeleted = false,
}: isFieldValueReadOnlyParams) => {
if (fieldName === 'noteTargets' || fieldName === 'taskTargets') {
return true;
}
if (isObjectRemote) {
return true;
}
if (isRecordDeleted) {
return true;
}
if (isWorkflowSubObjectMetadata(objectNameSingular)) {
return true;
}
if (
objectNameSingular === CoreObjectNameSingular.Workflow &&
fieldName !== 'name'
) {
return true;
}
if (
isDefined(fieldType) &&
(isFieldActor({ type: fieldType }) || isFieldRichText({ type: fieldType }))
) {
return true;
}
return false;
};

View File

@ -36,6 +36,7 @@ export const useRecordBoardRecordGqlFields = ({
const recordGqlFields: Record<string, any> = {
id: true,
deletedAt: true,
...Object.fromEntries(
visibleFieldDefinitions.map((visibleFieldDefinition) => [
visibleFieldDefinition.metadata.fieldName,

View File

@ -41,6 +41,7 @@ export const useRecordTableRecordGqlFields = ({
const recordGqlFields: Record<string, any> = {
id: true,
deletedAt: true,
...Object.fromEntries(
visibleTableColumns.map((column) => [column.metadata.fieldName, true]),
),

View File

@ -13,7 +13,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
import { useInlineCell } from '../hooks/useInlineCell';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { RecordInlineCellContainer } from './RecordInlineCellContainer';
import {
@ -26,21 +26,16 @@ type RecordInlineCellProps = {
loading?: boolean;
};
export const RecordInlineCell = ({
readonly,
loading,
}: RecordInlineCellProps) => {
export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
const { fieldDefinition, recordId, isCentered } = useContext(FieldContext);
const buttonIcon = useGetButtonIcon();
const isFieldInputOnly = useIsFieldInputOnly();
const isFieldReadOnly = useIsFieldReadOnly();
const isFieldReadOnly = useIsFieldValueReadOnly();
const { closeInlineCell } = useInlineCell();
const cellIsReadOnly = readonly || isFieldReadOnly;
const handleEnter: FieldInputEvent = (persistField) => {
persistField();
closeInlineCell();
@ -77,7 +72,7 @@ export const RecordInlineCell = ({
const { getIcon } = useIcons();
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
readonly: cellIsReadOnly,
readonly: isFieldReadOnly,
buttonIcon: buttonIcon,
customEditHotkeyScope: isFieldRelation(fieldDefinition)
? { scope: RelationPickerHotkeyScope.RelationPicker }
@ -102,7 +97,7 @@ export const RecordInlineCell = ({
onTab={handleTab}
onShiftTab={handleShiftTab}
onClickOutside={handleClickOutside}
isReadOnly={cellIsReadOnly}
isReadOnly={isFieldReadOnly}
/>
),
displayModeContent: <FieldDisplay />,

View File

@ -84,7 +84,6 @@ export const FieldsCard = ({
fieldMetadataItem.name === 'taskTargets')
),
);
const isReadOnly = objectMetadataItem.isRemote;
return (
<>
@ -149,10 +148,7 @@ export const FieldsCard = ({
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell
loading={recordLoading}
readonly={isReadOnly}
/>
<RecordInlineCell loading={recordLoading} />
</FieldContext.Provider>
))}
</>

View File

@ -1,6 +1,7 @@
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { RightDrawerTitleRecordInlineCell } from '@/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell';
@ -29,7 +30,6 @@ export const SummaryCard = ({
const {
recordFromStore,
recordLoading,
objectMetadataItem,
labelIdentifierFieldMetadataItem,
isPrefetchLoading,
recordIdentifier,
@ -47,7 +47,11 @@ export const SummaryCard = ({
const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
const isMobile = useIsMobile() || isInRightDrawer;
const isReadOnly = objectMetadataItem.isRemote;
const isReadOnly = isFieldValueReadOnly({
objectNameSingular,
isRecordDeleted: recordFromStore?.isDeleted,
});
if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
return <ShowPageSummaryCardSkeletonLoader />;

View File

@ -25,9 +25,9 @@ import {
RecordUpdateHook,
RecordUpdateHookParams,
} from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
@ -189,7 +189,7 @@ export const RecordDetailRelationRecordsListItem = ({
[isExpanded],
);
const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata);
const isReadOnly = useIsFieldValueReadOnly();
return (
<>
@ -206,7 +206,7 @@ export const RecordDetailRelationRecordsListItem = ({
accent="tertiary"
/>
</StyledClickableZone>
{canEdit && (
{!isReadOnly && (
<DropdownScope dropdownScopeId={dropdownScopeId}>
<Dropdown
dropdownId={dropdownScopeId}

View File

@ -7,11 +7,11 @@ import { IconForbid, IconPencil, IconPlus, LightIconButton } from 'twenty-ui';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect';
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly';
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
@ -34,7 +34,6 @@ import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams
import { View } from '@/views/types/View';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
type RecordDetailRelationSectionProps = {
loading: boolean;
};
@ -158,7 +157,7 @@ export const RecordDetailRelationSection = ({
recordId,
});
const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata);
const isReadOnly = useIsFieldValueReadOnly();
if (loading) return null;
@ -180,7 +179,7 @@ export const RecordDetailRelationSection = ({
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
areRecordsAvailable={relationRecords.length > 0}
rightAdornment={
canEdit && (
!isReadOnly && (
<DropdownScope dropdownScopeId={dropdownId}>
<StyledAddDropdown
dropdownId={dropdownId}

View File

@ -94,7 +94,6 @@ const meta: Meta = {
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
}) + mockPerformance.recordId,
isSelected: false,
isReadOnly: false,
isDragging: false,
dragHandleProps: null,
inView: true,

View File

@ -7,7 +7,6 @@ export type RecordTableRowContextProps = {
recordId: string;
rowIndex: number;
isSelected: boolean;
isReadOnly: boolean;
isPendingRow?: boolean;
isDragging: boolean;
dragHandleProps: DraggableProvidedDragHandleProps | null;

View File

@ -2,7 +2,7 @@ import { useContext } from 'react';
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
@ -21,7 +21,7 @@ export const RecordTableCellFieldInput = () => {
useContext(RecordTableContext);
const { recordId, fieldDefinition } = useContext(FieldContext);
const isFieldReadOnly = useIsFieldReadOnly();
const isFieldReadOnly = useIsFieldValueReadOnly();
const handleEnter: FieldInputEvent = (persistField) => {
onUpsertRecord({

View File

@ -10,7 +10,6 @@ import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEm
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { RecordTableCellButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellButton';
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
@ -22,7 +21,7 @@ import { isDefined } from '~/utils/isDefined';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
type RecordTableCellSoftFocusModeProps = {
@ -36,11 +35,8 @@ export const RecordTableCellSoftFocusMode = ({
}: RecordTableCellSoftFocusModeProps) => {
const { columnIndex } = useContext(RecordTableCellContext);
const closeCurrentTableCell = useCloseCurrentTableCellInEditMode();
const { isReadOnly: isRowReadOnly } = useContext(RecordTableRowContext);
const isFieldReadOnly = useIsFieldReadOnly();
const isCellReadOnly = isFieldReadOnly || isRowReadOnly;
const isFieldReadOnly = useIsFieldValueReadOnly();
const { openTableCell } = useOpenRecordTableCellFromCell();
@ -78,7 +74,7 @@ export const RecordTableCellSoftFocusMode = ({
useScopedHotkeys(
Key.Enter,
() => {
if (isCellReadOnly) {
if (isFieldReadOnly) {
return;
}
@ -95,7 +91,7 @@ export const RecordTableCellSoftFocusMode = ({
useScopedHotkeys(
'*',
(keyboardEvent) => {
if (isCellReadOnly) {
if (isFieldReadOnly) {
return;
}
@ -124,7 +120,7 @@ export const RecordTableCellSoftFocusMode = ({
);
const handleClick = () => {
if (!isFieldInputOnly && !isCellReadOnly) {
if (!isFieldInputOnly && !isFieldReadOnly) {
openTableCell();
}
};
@ -156,9 +152,9 @@ export const RecordTableCellSoftFocusMode = ({
isDefined(buttonIcon) &&
!editModeContentOnly &&
(!isFirstColumn || !isEmpty) &&
!isCellReadOnly;
!isFieldReadOnly;
const dontShowContent = isEmpty && isCellReadOnly;
const dontShowContent = isEmpty && isFieldReadOnly;
return (
<>

View File

@ -8,7 +8,6 @@ export const recordTableRow: RecordTableRowContextProps = {
recordId: 'recordId',
pathToShowPage: '/',
objectNameSingular: 'objectNameSingular',
isReadOnly: false,
dragHandleProps: {} as any,
isDragging: false,
inView: true,

View File

@ -1,7 +1,7 @@
import { useContext } from 'react';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
@ -32,11 +32,10 @@ export const useOpenRecordTableCellFromCell = () => {
const cellPosition = useCurrentTableCellPosition();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const { recordId, fieldDefinition } = useContext(FieldContext);
const { isReadOnly, pathToShowPage, objectNameSingular } = useContext(
const { pathToShowPage, objectNameSingular } = useContext(
RecordTableRowContext,
);
const isFieldReadOnly = useIsFieldReadOnly();
const cellIsReadOnly = isReadOnly || isFieldReadOnly;
const isFieldReadOnly = useIsFieldValueReadOnly();
const openTableCell = (
initialValue?: string,
@ -47,7 +46,7 @@ export const useOpenRecordTableCellFromCell = () => {
customCellHotkeyScope,
recordId,
fieldDefinition,
isReadOnly: cellIsReadOnly,
isReadOnly: isFieldReadOnly,
pathToShowPage,
objectNameSingular,
initialValue,

View File

@ -12,7 +12,7 @@ import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/
import { tableCellWidthsComponentState } from '@/object-record/record-table/states/tableCellWidthsComponentState';
import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
export const RecordTableRowWrapper = ({
recordId,
@ -48,7 +48,7 @@ export const RecordTableRowWrapper = ({
rootMargin: '1000px',
});
const [, setTableCellWidths] = useRecoilComponentStateV2(
const setTableCellWidths = useSetRecoilComponentStateV2(
tableCellWidthsComponentState,
);
@ -108,7 +108,6 @@ export const RecordTableRowWrapper = ({
}) + recordId,
objectNameSingular: objectMetadataItem.nameSingular,
isSelected: currentRowSelected,
isReadOnly: objectMetadataItem.isRemote ?? false,
isPendingRow,
isDragging: draggableSnapshot.isDragging,
dragHandleProps: draggableProvided.dragHandleProps,

View File

@ -0,0 +1,20 @@
import { ReactNode, useEffect } from 'react';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
export const JestRecordStoreSetter = ({
children,
records,
}: {
children: ReactNode;
records: ObjectRecord[];
}) => {
const { upsertRecords } = useUpsertRecordsInStore();
useEffect(() => {
upsertRecords(records);
});
return <>{children}</>;
};