Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: soft deleted records are read only #8198

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,6 @@ export const actorFieldDefinition: FieldDefinition<FieldActorMetadata> = {
defaultValue: { source: 'MANUAL', name: '' },
metadata: {
fieldName: 'actor',
objectMetadataNameSingular: 'person',
},
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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);
});
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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,
});
};
Original file line number Diff line number Diff line change
@@ -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);
});
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const useRecordBoardRecordGqlFields = ({

const recordGqlFields: Record<string, any> = {
id: true,
deletedAt: true,
...Object.fromEntries(
visibleFieldDefinitions.map((visibleFieldDefinition) => [
visibleFieldDefinition.metadata.fieldName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
),
Expand Down
Loading
Loading