fix: fix several field bugs (#5339)

After discussing with @charlesBochet, several fixes are needed on
fields:
- [x] Disable Boolean field `defaultValue` edition for now (On
`defaultValue` update, newly created records are not taking the updated
`defaultValue` into account. Setting the `defaultValue` on creation is
fine.)
- [x] Disable Phone field creation for now
- [x] For the Person object, display the "Phone" field as a field of
type Phone (right now its type is Text; later we'll migrate it to a
proper Phone field).
- [x] Fix RawJson field display (displaying `[object Object]` in Record
Table cells).
- [x] In Settings/Data Model, on Relation field creation/edition,
"Object destination" select is not working properly if an object was not
manually selected (displays Companies by default but creates a relation
to another random object than Companies).
This commit is contained in:
Thaïs
2024-05-09 01:56:15 +02:00
committed by GitHub
parent 005045c596
commit 7728c09dba
23 changed files with 332 additions and 167 deletions

View File

@ -1,3 +1,5 @@
import pick from 'lodash.pick';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@ -12,15 +14,11 @@ export const getRecordFromRecordNode = <T extends ObjectRecord>({
return { return {
...Object.fromEntries( ...Object.fromEntries(
Object.entries(recordNode).map(([fieldName, value]) => { Object.entries(recordNode).map(([fieldName, value]) => {
if (isUndefinedOrNull(value)) { if (
return [fieldName, value]; isUndefinedOrNull(value) ||
} Array.isArray(value) ||
typeof value !== 'object'
if (Array.isArray(value)) { ) {
return [fieldName, value];
}
if (typeof value !== 'object') {
return [fieldName, value]; return [fieldName, value];
} }
@ -32,7 +30,10 @@ export const getRecordFromRecordNode = <T extends ObjectRecord>({
: [fieldName, getRecordFromRecordNode<T>({ recordNode: value })]; : [fieldName, getRecordFromRecordNode<T>({ recordNode: value })];
}), }),
), ),
id: recordNode.id, // Only adds `id` and `__typename` if they exist.
__typename: recordNode.__typename, // RawJson field value passes through this method and does not have `id` or `__typename`.
// This prevents adding an undefined `id` and `__typename` to the RawJson field value,
// which is invalid JSON.
...pick(recordNode, ['id', '__typename'] as const),
} as T; } as T;
}; };

View File

@ -1,6 +1,7 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay'; import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { ExpandableListProps } from '@/ui/layout/expandable-list/components/ExpandableList'; import { ExpandableListProps } from '@/ui/layout/expandable-list/components/ExpandableList';
@ -56,7 +57,8 @@ export const FieldDisplay = ({
<ChipFieldDisplay /> <ChipFieldDisplay />
) : isFieldRelation(fieldDefinition) ? ( ) : isFieldRelation(fieldDefinition) ? (
<RelationFieldDisplay /> <RelationFieldDisplay />
) : isFieldPhone(fieldDefinition) ? ( ) : isFieldPhone(fieldDefinition) ||
isFieldDisplayedAsPhone(fieldDefinition) ? (
<PhoneFieldDisplay /> <PhoneFieldDisplay />
) : isFieldText(fieldDefinition) ? ( ) : isFieldText(fieldDefinition) ? (
<TextFieldDisplay /> <TextFieldDisplay />

View File

@ -9,6 +9,7 @@ import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input
import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput'; import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput';
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate';
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect'; import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
@ -71,7 +72,8 @@ export const FieldInput = ({
> >
{isFieldRelation(fieldDefinition) ? ( {isFieldRelation(fieldDefinition) ? (
<RelationFieldInput onSubmit={onSubmit} onCancel={onCancel} /> <RelationFieldInput onSubmit={onSubmit} onCancel={onCancel} />
) : isFieldPhone(fieldDefinition) ? ( ) : isFieldPhone(fieldDefinition) ||
isFieldDisplayedAsPhone(fieldDefinition) ? (
<PhoneFieldInput <PhoneFieldInput
onEnter={onEnter} onEnter={onEnter}
onEscape={onEscape} onEscape={onEscape}

View File

@ -1,6 +1,7 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { IconComponent, IconPencil } from 'twenty-ui'; import { IconComponent, IconPencil } from 'twenty-ui';
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect'; import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
@ -20,6 +21,7 @@ export const useGetButtonIcon = (): IconComponent | undefined => {
isFieldLink(fieldDefinition) || isFieldLink(fieldDefinition) ||
isFieldEmail(fieldDefinition) || isFieldEmail(fieldDefinition) ||
isFieldPhone(fieldDefinition) || isFieldPhone(fieldDefinition) ||
isFieldDisplayedAsPhone(fieldDefinition) ||
isFieldMultiSelect(fieldDefinition) || isFieldMultiSelect(fieldDefinition) ||
(isFieldRelation(fieldDefinition) && (isFieldRelation(fieldDefinition) &&
fieldDefinition.metadata.relationObjectMetadataNameSingular !== fieldDefinition.metadata.relationObjectMetadataNameSingular !==

View File

@ -1,4 +1,5 @@
import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField'; import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField';
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay'; import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay';
export const JsonFieldDisplay = () => { export const JsonFieldDisplay = () => {
@ -6,7 +7,7 @@ export const JsonFieldDisplay = () => {
return ( return (
<JsonDisplay <JsonDisplay
text={fieldValue ? JSON.stringify(JSON.parse(fieldValue), null, 2) : ''} text={isFieldRawJsonValue(fieldValue) ? JSON.stringify(fieldValue) : ''}
maxWidth={maxWidth} maxWidth={maxWidth}
/> />
); );

View File

@ -1,6 +1,7 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata'; import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
@ -9,7 +10,6 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldContext } from '../../contexts/FieldContext'; import { FieldContext } from '../../contexts/FieldContext';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldRawJson } from '../../types/guards/isFieldRawJson'; import { isFieldRawJson } from '../../types/guards/isFieldRawJson';
import { isFieldTextValue } from '../../types/guards/isFieldTextValue';
export const useJsonField = () => { export const useJsonField = () => {
const { entityId, fieldDefinition, hotkeyScope, maxWidth } = const { entityId, fieldDefinition, hotkeyScope, maxWidth } =
@ -29,7 +29,18 @@ export const useJsonField = () => {
fieldName: fieldName, fieldName: fieldName,
}), }),
); );
const fieldTextValue = isFieldTextValue(fieldValue) ? fieldValue : '';
const persistField = usePersistField();
const persistJsonField = (nextValue: string) => {
if (!nextValue) persistField(null);
try {
persistField(JSON.parse(nextValue));
} catch {
// Do nothing
}
};
const { setDraftValue, getDraftValueSelector } = const { setDraftValue, getDraftValueSelector } =
useRecordFieldInput<FieldJsonValue>(`${entityId}-${fieldName}`); useRecordFieldInput<FieldJsonValue>(`${entityId}-${fieldName}`);
@ -41,8 +52,9 @@ export const useJsonField = () => {
setDraftValue, setDraftValue,
maxWidth, maxWidth,
fieldDefinition, fieldDefinition,
fieldValue: fieldTextValue, fieldValue,
setFieldValue, setFieldValue,
hotkeyScope, hotkeyScope,
persistJsonField,
}; };
}; };

View File

@ -4,6 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldPhoneValue } from '@/object-record/record-field/types/FieldMetadata'; import { FieldPhoneValue } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -15,7 +16,17 @@ import { isFieldPhone } from '../../types/guards/isFieldPhone';
export const usePhoneField = () => { export const usePhoneField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
assertFieldMetadata(FieldMetadataType.Phone, isFieldPhone, fieldDefinition); try {
// TODO: temporary - remove when 'Phone' field in 'Person' object
// is migrated to use FieldMetadataType.Phone as type.
assertFieldMetadata(
FieldMetadataType.Text,
isFieldDisplayedAsPhone,
fieldDefinition,
);
} catch {
assertFieldMetadata(FieldMetadataType.Phone, isFieldPhone, fieldDefinition);
}
const fieldName = fieldDefinition.metadata.fieldName; const fieldName = fieldDefinition.metadata.fieldName;

View File

@ -1,8 +1,6 @@
import { isValidJSON } from '@/object-record/record-field/utils/isFieldValueJson';
import { FieldTextAreaOverlay } from '@/ui/field/input/components/FieldTextAreaOverlay'; import { FieldTextAreaOverlay } from '@/ui/field/input/components/FieldTextAreaOverlay';
import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput'; import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
import { usePersistField } from '../../../hooks/usePersistField';
import { useJsonField } from '../../hooks/useJsonField'; import { useJsonField } from '../../hooks/useJsonField';
import { FieldInputEvent } from './DateFieldInput'; import { FieldInputEvent } from './DateFieldInput';
@ -22,53 +20,47 @@ export const RawJsonFieldInput = ({
onTab, onTab,
onShiftTab, onShiftTab,
}: RawJsonFieldInputProps) => { }: RawJsonFieldInputProps) => {
const { fieldDefinition, draftValue, hotkeyScope, setDraftValue } = const {
useJsonField(); fieldDefinition,
draftValue,
const persistField = usePersistField(); hotkeyScope,
setDraftValue,
const handlePersistField = (newText: string) => { persistJsonField,
if (!newText || isValidJSON(newText)) persistField(newText || null); } = useJsonField();
};
const handleEnter = (newText: string) => { const handleEnter = (newText: string) => {
onEnter?.(() => handlePersistField(newText)); onEnter?.(() => persistJsonField(newText));
}; };
const handleEscape = (newText: string) => { const handleEscape = (newText: string) => {
onEscape?.(() => handlePersistField(newText)); onEscape?.(() => persistJsonField(newText));
}; };
const handleClickOutside = ( const handleClickOutside = (
_event: MouseEvent | TouchEvent, _event: MouseEvent | TouchEvent,
newText: string, newText: string,
) => { ) => {
onClickOutside?.(() => handlePersistField(newText)); onClickOutside?.(() => persistJsonField(newText));
}; };
const handleTab = (newText: string) => { const handleTab = (newText: string) => {
onTab?.(() => handlePersistField(newText)); onTab?.(() => persistJsonField(newText));
}; };
const handleShiftTab = (newText: string) => { const handleShiftTab = (newText: string) => {
onShiftTab?.(() => handlePersistField(newText)); onShiftTab?.(() => persistJsonField(newText));
}; };
const handleChange = (newText: string) => { const handleChange = (newText: string) => {
setDraftValue(newText); setDraftValue(newText);
}; };
const value =
draftValue && isValidJSON(draftValue)
? JSON.stringify(JSON.parse(draftValue), null, 2)
: draftValue ?? '';
return ( return (
<FieldTextAreaOverlay> <FieldTextAreaOverlay>
<TextAreaInput <TextAreaInput
placeholder={fieldDefinition.metadata.placeHolder} placeholder={fieldDefinition.metadata.placeHolder}
autoFocus autoFocus
value={value} value={draftValue ?? ''}
onClickOutside={handleClickOutside} onClickOutside={handleClickOutside}
onEnter={handleEnter} onEnter={handleEnter}
onEscape={handleEscape} onEscape={handleEscape}

View File

@ -6,6 +6,7 @@ import {
FieldDateTimeValue, FieldDateTimeValue,
FieldEmailValue, FieldEmailValue,
FieldFullNameValue, FieldFullNameValue,
FieldJsonValue,
FieldLinksValue, FieldLinksValue,
FieldLinkValue, FieldLinkValue,
FieldMultiSelectValue, FieldMultiSelectValue,
@ -47,6 +48,7 @@ export type FieldAddressDraftValue = {
addressLat: number | null; addressLat: number | null;
addressLng: number | null; addressLng: number | null;
}; };
export type FieldJsonDraftValue = string;
export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue
? FieldTextDraftValue ? FieldTextDraftValue
@ -80,4 +82,6 @@ export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue
? FieldRelationDraftValue ? FieldRelationDraftValue
: FieldValue extends FieldAddressValue : FieldValue extends FieldAddressValue
? FieldAddressDraftValue ? FieldAddressDraftValue
: never; : FieldValue extends FieldJsonValue
? FieldJsonDraftValue
: never;

View File

@ -173,4 +173,8 @@ export type FieldSelectValue = string | null;
export type FieldMultiSelectValue = string[] | null; export type FieldMultiSelectValue = string[] | null;
export type FieldRelationValue = EntityForSelect | null; export type FieldRelationValue = EntityForSelect | null;
export type FieldJsonValue = string;
// See https://zod.dev/?id=json-type
type Literal = string | number | boolean | null;
export type Json = Literal | { [key: string]: Json } | Json[];
export type FieldJsonValue = Record<string, Json> | Json[] | null;

View File

@ -0,0 +1,14 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldTextMetadata } from '../FieldMetadata';
// TODO: temporary - remove when 'Phone' field in 'Person' object
// is migrated to use FieldMetadataType.Phone as type.
export const isFieldDisplayedAsPhone = (
field: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
): field is FieldDefinition<FieldTextMetadata> =>
field.metadata.objectMetadataNameSingular === CoreObjectNameSingular.Person &&
field.type === FieldMetadataType.Text &&
field.metadata.fieldName === 'phone';

View File

@ -1,8 +1,20 @@
import { isNull, isString } from '@sniptt/guards'; import { z } from 'zod';
import { FieldJsonValue } from '../FieldMetadata'; import { FieldJsonValue, Json } from '../FieldMetadata';
// See https://zod.dev/?id=json-type
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]),
);
export const jsonWithoutLiteralsSchema: z.ZodType<FieldJsonValue> = z.union([
z.null(), // Exclude literal values other than null
z.array(jsonSchema),
z.record(jsonSchema),
]);
// TODO: add zod
export const isFieldRawJsonValue = ( export const isFieldRawJsonValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldJsonValue => isString(fieldValue) || isNull(fieldValue); ): fieldValue is FieldJsonValue =>
jsonWithoutLiteralsSchema.safeParse(fieldValue).success;

View File

@ -3,6 +3,8 @@ import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldIn
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
import { computeEmptyDraftValue } from '@/object-record/record-field/utils/computeEmptyDraftValue'; import { computeEmptyDraftValue } from '@/object-record/record-field/utils/computeEmptyDraftValue';
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty'; import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
@ -33,9 +35,20 @@ export const computeDraftValueFromFieldValue = <FieldValue>({
currencyCode: fieldValue?.currencyCode ?? '', currencyCode: fieldValue?.currencyCode ?? '',
} as unknown as FieldInputDraftValue<FieldValue>; } as unknown as FieldInputDraftValue<FieldValue>;
} }
if (isFieldRelation(fieldDefinition)) { if (isFieldRelation(fieldDefinition)) {
return computeEmptyDraftValue<FieldValue>({ fieldDefinition }); return computeEmptyDraftValue<FieldValue>({ fieldDefinition });
} }
if (isFieldRawJson(fieldDefinition)) {
return isFieldRawJsonValue(fieldValue)
? (JSON.stringify(
fieldValue,
null,
2,
) as FieldInputDraftValue<FieldValue>)
: computeEmptyDraftValue<FieldValue>({ fieldDefinition });
}
return fieldValue as FieldInputDraftValue<FieldValue>; return fieldValue as FieldInputDraftValue<FieldValue>;
}; };

View File

@ -8,7 +8,8 @@ import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldE
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink';
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
import { isFieldRelationValue } from '@/object-record/record-field/types/guards/isFieldRelationValue'; import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
@ -26,7 +27,8 @@ export const computeEmptyDraftValue = <FieldValue>({
isFieldDateTime(fieldDefinition) || isFieldDateTime(fieldDefinition) ||
isFieldNumber(fieldDefinition) || isFieldNumber(fieldDefinition) ||
isFieldEmail(fieldDefinition) || isFieldEmail(fieldDefinition) ||
isFieldRelationValue(fieldDefinition) isFieldRelation(fieldDefinition) ||
isFieldRawJson(fieldDefinition)
) { ) {
return '' as FieldInputDraftValue<FieldValue>; return '' as FieldInputDraftValue<FieldValue>;
} }

View File

@ -1,12 +0,0 @@
import { isString } from '@sniptt/guards';
export const isValidJSON = (str: string) => {
try {
if (isString(JSON.parse(str))) {
throw new Error(`Strings are not supported as JSON: ${str}`);
}
return true;
} catch (error) {
return false;
}
};

View File

@ -6,6 +6,7 @@ import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { Select } from '@/ui/input/components/Select'; import { Select } from '@/ui/input/components/Select';
import { CardContent } from '@/ui/layout/card/components/CardContent'; import { CardContent } from '@/ui/layout/card/components/CardContent';
import { isDefined } from '~/utils/isDefined';
// TODO: rename to SettingsDataModelFieldBooleanForm and move to settings/data-model/fields/forms/components // TODO: rename to SettingsDataModelFieldBooleanForm and move to settings/data-model/fields/forms/components
@ -41,6 +42,7 @@ export const SettingsDataModelFieldBooleanForm = ({
}: SettingsDataModelFieldBooleanFormProps) => { }: SettingsDataModelFieldBooleanFormProps) => {
const { control } = useFormContext<SettingsDataModelFieldBooleanFormValues>(); const { control } = useFormContext<SettingsDataModelFieldBooleanFormValues>();
const isEditMode = isDefined(fieldMetadataItem?.defaultValue);
const initialValue = fieldMetadataItem?.defaultValue ?? true; const initialValue = fieldMetadataItem?.defaultValue ?? true;
return ( return (
@ -54,6 +56,9 @@ export const SettingsDataModelFieldBooleanForm = ({
<Select <Select
className={className} className={className}
fullWidth fullWidth
// TODO: temporary fix - disabling edition because after editing the defaultValue,
// newly created records are not taking into account the updated defaultValue properly.
disabled={isEditMode}
dropdownId="object-field-default-value-select" dropdownId="object-field-default-value-select"
value={value} value={value}
onChange={onChange} onChange={onChange}

View File

@ -1,18 +1,16 @@
import { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useIcons } from 'twenty-ui'; import { useIcons } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema'; import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
import { useRelationSettingsFormInitialValues } from '@/settings/data-model/fields/forms/hooks/useRelationSettingsFormInitialValues';
import { IconPicker } from '@/ui/input/components/IconPicker'; import { IconPicker } from '@/ui/input/components/IconPicker';
import { Select } from '@/ui/input/components/Select'; import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { RelationMetadataType } from '~/generated-metadata/graphql';
import { RELATION_TYPES } from '../constants/RelationTypes'; import { RELATION_TYPES } from '../constants/RelationTypes';
import { RelationType } from '../types/RelationType'; import { RelationType } from '../types/RelationType';
@ -32,7 +30,7 @@ export const settingsDataModelFieldRelationFormSchema = z.object({
}), }),
}); });
type SettingsDataModelFieldRelationFormValues = z.infer< export type SettingsDataModelFieldRelationFormValues = z.infer<
typeof settingsDataModelFieldRelationFormSchema typeof settingsDataModelFieldRelationFormSchema
>; >;
@ -79,30 +77,23 @@ const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES)
export const SettingsDataModelFieldRelationForm = ({ export const SettingsDataModelFieldRelationForm = ({
fieldMetadataItem, fieldMetadataItem,
}: SettingsDataModelFieldRelationFormProps) => { }: SettingsDataModelFieldRelationFormProps) => {
const { control } = const { control, watch: watchFormValue } =
useFormContext<SettingsDataModelFieldRelationFormValues>(); useFormContext<SettingsDataModelFieldRelationFormValues>();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { objectMetadataItems } = useFilteredObjectMetadataItems(); const { objectMetadataItems, findObjectMetadataItemById } =
useFilteredObjectMetadataItems();
const getRelationMetadata = useGetRelationMetadata();
const { const {
relationFieldMetadataItem, disableFieldEdition,
relationType, disableRelationEdition,
relationObjectMetadataItem, initialRelationFieldMetadataItem,
} = initialRelationObjectMetadataItem,
useMemo( initialRelationType,
() => } = useRelationSettingsFormInitialValues({ fieldMetadataItem });
fieldMetadataItem ? getRelationMetadata({ fieldMetadataItem }) : null,
[fieldMetadataItem, getRelationMetadata],
) ?? {};
const disableFieldEdition = const selectedObjectMetadataItem = findObjectMetadataItemById(
relationFieldMetadataItem && !relationFieldMetadataItem.isCustom; watchFormValue('relation.objectMetadataId'),
);
const disableRelationEdition = !!relationFieldMetadataItem;
const selectedObjectMetadataItem =
relationObjectMetadataItem ?? objectMetadataItems[0];
return ( return (
<StyledContainer> <StyledContainer>
@ -110,7 +101,7 @@ export const SettingsDataModelFieldRelationForm = ({
<Controller <Controller
name="relation.type" name="relation.type"
control={control} control={control}
defaultValue={relationType ?? RelationMetadataType.OneToMany} defaultValue={initialRelationType}
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Select <Select
label="Relation type" label="Relation type"
@ -126,7 +117,7 @@ export const SettingsDataModelFieldRelationForm = ({
<Controller <Controller
name="relation.objectMetadataId" name="relation.objectMetadataId"
control={control} control={control}
defaultValue={selectedObjectMetadataItem?.id} defaultValue={initialRelationObjectMetadataItem.id}
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Select <Select
label="Object destination" label="Object destination"
@ -153,11 +144,7 @@ export const SettingsDataModelFieldRelationForm = ({
<Controller <Controller
name="relation.field.icon" name="relation.field.icon"
control={control} control={control}
defaultValue={ defaultValue={initialRelationFieldMetadataItem.icon}
relationFieldMetadataItem?.icon ??
relationObjectMetadataItem?.icon ??
'IconUsers'
}
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<IconPicker <IconPicker
disabled={disableFieldEdition} disabled={disableFieldEdition}
@ -171,7 +158,7 @@ export const SettingsDataModelFieldRelationForm = ({
<Controller <Controller
name="relation.field.label" name="relation.field.label"
control={control} control={control}
defaultValue={relationFieldMetadataItem?.label} defaultValue={initialRelationFieldMetadataItem.label}
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<TextInput <TextInput
disabled={disableFieldEdition} disabled={disableFieldEdition}

View File

@ -0,0 +1,109 @@
import { useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
import {
SettingsDataModelFieldRelationForm,
SettingsDataModelFieldRelationFormValues,
} from '@/settings/data-model/components/SettingsObjectFieldRelationForm';
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import { useRelationSettingsFormInitialValues } from '@/settings/data-model/fields/forms/hooks/useRelationSettingsFormInitialValues';
import {
SettingsDataModelFieldPreviewCard,
SettingsDataModelFieldPreviewCardProps,
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type SettingsDataModelFieldRelationSettingsFormCardProps = {
fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> &
Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>;
relationFieldMetadataItem?: FieldMetadataItem;
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
display: grid;
flex: 1 1 100%;
`;
const StyledPreviewContent = styled.div`
display: flex;
gap: 6px;
`;
const StyledRelationImage = styled.img<{ flip?: boolean }>`
transform: ${({ flip }) => (flip ? 'scaleX(-1)' : 'none')};
width: 54px;
`;
export const SettingsDataModelFieldRelationSettingsFormCard = ({
fieldMetadataItem,
objectMetadataItem,
}: SettingsDataModelFieldRelationSettingsFormCardProps) => {
const { watch: watchFormValue } =
useFormContext<SettingsDataModelFieldRelationFormValues>();
const { findObjectMetadataItemById } = useFilteredObjectMetadataItems();
const {
initialRelationObjectMetadataItem,
initialRelationType,
initialRelationFieldMetadataItem,
} = useRelationSettingsFormInitialValues({ fieldMetadataItem });
const relationObjectMetadataId = watchFormValue(
'relation.objectMetadataId',
initialRelationObjectMetadataItem?.id,
);
const relationObjectMetadataItem = findObjectMetadataItemById(
relationObjectMetadataId,
);
if (!relationObjectMetadataItem) return null;
const relationType = watchFormValue('relation.type', initialRelationType);
const relationTypeConfig = RELATION_TYPES[relationType];
return (
<SettingsDataModelPreviewFormCard
preview={
<StyledPreviewContent>
<StyledFieldPreviewCard
fieldMetadataItem={fieldMetadataItem}
shrink
objectMetadataItem={objectMetadataItem}
relationObjectMetadataItem={relationObjectMetadataItem}
/>
<StyledRelationImage
src={relationTypeConfig.imageSrc}
flip={relationTypeConfig.isImageFlipped}
alt={relationTypeConfig.label}
/>
<StyledFieldPreviewCard
fieldMetadataItem={{
...initialRelationFieldMetadataItem,
icon: watchFormValue(
'relation.field.icon',
initialRelationFieldMetadataItem.icon,
),
label:
watchFormValue(
'relation.field.label',
initialRelationFieldMetadataItem.label,
) || 'Field name',
type: FieldMetadataType.Relation,
}}
shrink
objectMetadataItem={relationObjectMetadataItem}
relationObjectMetadataItem={objectMetadataItem}
/>
</StyledPreviewContent>
}
form={
<SettingsDataModelFieldRelationForm
fieldMetadataItem={fieldMetadataItem}
/>
}
/>
);
};

View File

@ -3,7 +3,6 @@ import styled from '@emotion/styled';
import omit from 'lodash.omit'; import omit from 'lodash.omit';
import { z } from 'zod'; import { z } from 'zod';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { import {
SettingsDataModelFieldBooleanForm, SettingsDataModelFieldBooleanForm,
@ -14,16 +13,13 @@ import {
SettingsDataModelFieldCurrencyForm, SettingsDataModelFieldCurrencyForm,
settingsDataModelFieldCurrencyFormSchema, settingsDataModelFieldCurrencyFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldCurrencyForm'; } from '@/settings/data-model/components/SettingsObjectFieldCurrencyForm';
import { import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/components/SettingsObjectFieldRelationForm';
SettingsDataModelFieldRelationForm,
settingsDataModelFieldRelationFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldRelationForm';
import { import {
SettingsDataModelFieldSelectForm, SettingsDataModelFieldSelectForm,
settingsDataModelFieldSelectFormSchema, settingsDataModelFieldSelectFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldSelectForm'; } from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldRelationSettingsFormCard';
import { import {
SettingsDataModelFieldPreviewCard, SettingsDataModelFieldPreviewCard,
SettingsDataModelFieldPreviewCardProps, SettingsDataModelFieldPreviewCardProps,
@ -81,7 +77,6 @@ type SettingsDataModelFieldSettingsFormCardProps = {
disableCurrencyForm?: boolean; disableCurrencyForm?: boolean;
fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> & fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> &
Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>; Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>;
relationFieldMetadataItem?: FieldMetadataItem;
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>; } & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
@ -94,11 +89,6 @@ const StyledPreviewContent = styled.div`
gap: 6px; gap: 6px;
`; `;
const StyledRelationImage = styled.img<{ flip?: boolean }>`
transform: ${({ flip }) => (flip ? 'scaleX(-1)' : 'none')};
width: 54px;
`;
const previewableTypes = [ const previewableTypes = [
FieldMetadataType.Boolean, FieldMetadataType.Boolean,
FieldMetadataType.Currency, FieldMetadataType.Currency,
@ -121,23 +111,20 @@ export const SettingsDataModelFieldSettingsFormCard = ({
disableCurrencyForm, disableCurrencyForm,
fieldMetadataItem, fieldMetadataItem,
objectMetadataItem, objectMetadataItem,
relationFieldMetadataItem,
}: SettingsDataModelFieldSettingsFormCardProps) => { }: SettingsDataModelFieldSettingsFormCardProps) => {
const { watch: watchFormValue } = const { watch: watchFormValue } =
useFormContext<SettingsDataModelFieldSettingsFormValues>(); useFormContext<SettingsDataModelFieldSettingsFormValues>();
const { findObjectMetadataItemById } = useFilteredObjectMetadataItems();
if (!previewableTypes.includes(fieldMetadataItem.type)) return null; if (!previewableTypes.includes(fieldMetadataItem.type)) return null;
const relationObjectMetadataId = watchFormValue('relation.objectMetadataId'); if (fieldMetadataItem.type === FieldMetadataType.Relation) {
const relationObjectMetadataItem = relationObjectMetadataId return (
? findObjectMetadataItemById(relationObjectMetadataId) <SettingsDataModelFieldRelationSettingsFormCard
: undefined; fieldMetadataItem={fieldMetadataItem}
objectMetadataItem={objectMetadataItem}
const relationType = watchFormValue('relation.type'); />
const relationTypeConfig = relationType );
? RELATION_TYPES[relationType] }
: undefined;
return ( return (
<SettingsDataModelPreviewFormCard <SettingsDataModelPreviewFormCard
@ -145,34 +132,9 @@ export const SettingsDataModelFieldSettingsFormCard = ({
<StyledPreviewContent> <StyledPreviewContent>
<StyledFieldPreviewCard <StyledFieldPreviewCard
fieldMetadataItem={fieldMetadataItem} fieldMetadataItem={fieldMetadataItem}
shrink={fieldMetadataItem.type === FieldMetadataType.Relation}
objectMetadataItem={objectMetadataItem} objectMetadataItem={objectMetadataItem}
relationObjectMetadataItem={relationObjectMetadataItem}
selectOptions={watchFormValue('options')} selectOptions={watchFormValue('options')}
/> />
{fieldMetadataItem.type === FieldMetadataType.Relation &&
!!relationObjectMetadataItem &&
!!relationTypeConfig && (
<>
<StyledRelationImage
src={relationTypeConfig.imageSrc}
flip={relationTypeConfig.isImageFlipped}
alt={relationTypeConfig.label}
/>
<StyledFieldPreviewCard
fieldMetadataItem={{
...relationFieldMetadataItem,
icon: watchFormValue('relation.field.icon'),
label:
watchFormValue('relation.field.label') || 'Field name',
type: FieldMetadataType.Relation,
}}
shrink
objectMetadataItem={relationObjectMetadataItem}
relationObjectMetadataItem={objectMetadataItem}
/>
</>
)}
</StyledPreviewContent> </StyledPreviewContent>
} }
form={ form={
@ -185,10 +147,6 @@ export const SettingsDataModelFieldSettingsFormCard = ({
disabled={disableCurrencyForm} disabled={disableCurrencyForm}
fieldMetadataItem={fieldMetadataItem} fieldMetadataItem={fieldMetadataItem}
/> />
) : fieldMetadataItem.type === FieldMetadataType.Relation ? (
<SettingsDataModelFieldRelationForm
fieldMetadataItem={fieldMetadataItem}
/>
) : fieldMetadataItem.type === FieldMetadataType.Select || ) : fieldMetadataItem.type === FieldMetadataType.Select ||
fieldMetadataItem.type === FieldMetadataType.MultiSelect ? ( fieldMetadataItem.type === FieldMetadataType.MultiSelect ? (
<SettingsDataModelFieldSelectForm <SettingsDataModelFieldSelectForm

View File

@ -7,10 +7,7 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata';
mockedCompanyObjectMetadataItem,
mockedPersonObjectMetadataItem,
} from '~/testing/mock-data/metadata';
import { SettingsDataModelFieldSettingsFormCard } from '../SettingsDataModelFieldSettingsFormCard'; import { SettingsDataModelFieldSettingsFormCard } from '../SettingsDataModelFieldSettingsFormCard';
@ -49,9 +46,6 @@ export const WithRelationForm: Story = {
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
({ name }) => name === 'people', ({ name }) => name === 'people',
), ),
relationFieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
({ name }) => name === 'company',
)!,
}, },
}; };

View File

@ -0,0 +1,55 @@
import { useMemo } from 'react';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { RelationMetadataType } from '~/generated-metadata/graphql';
export const useRelationSettingsFormInitialValues = ({
fieldMetadataItem,
}: {
fieldMetadataItem?: Pick<
FieldMetadataItem,
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
>;
}) => {
const { objectMetadataItems } = useFilteredObjectMetadataItems();
const getRelationMetadata = useGetRelationMetadata();
const {
relationFieldMetadataItem,
relationObjectMetadataItem: relationObjectMetadataItemFromFieldMetadata,
relationType: relationTypeFromFieldMetadata,
} = useMemo(
() =>
fieldMetadataItem ? getRelationMetadata({ fieldMetadataItem }) : null,
[fieldMetadataItem, getRelationMetadata],
) ?? {};
const initialRelationObjectMetadataItem = useMemo(
() =>
relationObjectMetadataItemFromFieldMetadata ??
objectMetadataItems.find(
({ nameSingular }) => nameSingular === CoreObjectNameSingular.Person,
) ??
objectMetadataItems.filter(isObjectMetadataAvailableForRelation)[0],
[objectMetadataItems, relationObjectMetadataItemFromFieldMetadata],
);
const initialRelationType =
relationTypeFromFieldMetadata ?? RelationMetadataType.OneToMany;
return {
disableFieldEdition:
relationFieldMetadataItem && !relationFieldMetadataItem.isCustom,
disableRelationEdition: !!relationFieldMetadataItem,
initialRelationFieldMetadataItem: relationFieldMetadataItem ?? {
icon: initialRelationObjectMetadataItem.icon ?? 'IconUsers',
label: '',
},
initialRelationObjectMetadataItem,
initialRelationType,
};
};

View File

@ -1,9 +1,8 @@
import { useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { isNonEmptyString } from '@sniptt/guards';
import omit from 'lodash.omit'; import omit from 'lodash.omit';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import { IconArchive, IconSettings } from 'twenty-ui'; import { IconArchive, IconSettings } from 'twenty-ui';
@ -33,6 +32,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
type SettingsDataModelFieldEditFormValues = z.infer< type SettingsDataModelFieldEditFormValues = z.infer<
typeof settingsFieldFormSchema typeof settingsFieldFormSchema
@ -72,15 +72,6 @@ export const SettingsObjectFieldEdit = () => {
); );
const getRelationMetadata = useGetRelationMetadata(); const getRelationMetadata = useGetRelationMetadata();
const { relationFieldMetadataItem } =
useMemo(
() =>
activeMetadataField
? getRelationMetadata({ fieldMetadataItem: activeMetadataField })
: null,
[activeMetadataField, getRelationMetadata],
) ?? {};
const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem(); const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem();
const formConfig = useForm<SettingsDataModelFieldEditFormValues>({ const formConfig = useForm<SettingsDataModelFieldEditFormValues>({
@ -111,13 +102,19 @@ export const SettingsObjectFieldEdit = () => {
if ( if (
formValues.type === FieldMetadataType.Relation && formValues.type === FieldMetadataType.Relation &&
'relation' in formValues && 'relation' in formValues &&
'relation' in dirtyFields && 'relation' in dirtyFields
isNonEmptyString(relationFieldMetadataItem?.id)
) { ) {
await updateOneFieldMetadataItem({ const { relationFieldMetadataItem } =
fieldMetadataIdToUpdate: relationFieldMetadataItem.id, getRelationMetadata({
updatePayload: formValues.relation.field, fieldMetadataItem: activeMetadataField,
}); }) ?? {};
if (isDefined(relationFieldMetadataItem)) {
await updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
updatePayload: formValues.relation.field,
});
}
} }
const otherDirtyFields = omit(dirtyFields, 'relation'); const otherDirtyFields = omit(dirtyFields, 'relation');
@ -202,7 +199,6 @@ export const SettingsObjectFieldEdit = () => {
disableCurrencyForm disableCurrencyForm
fieldMetadataItem={activeMetadataField} fieldMetadataItem={activeMetadataField}
objectMetadataItem={activeObjectMetadataItem} objectMetadataItem={activeObjectMetadataItem}
relationFieldMetadataItem={relationFieldMetadataItem}
/> />
</Section> </Section>
{!isLabelIdentifier && ( {!isLabelIdentifier && (

View File

@ -271,6 +271,7 @@ export const SettingsObjectNewFieldStep2 = () => {
FieldMetadataType.Numeric, FieldMetadataType.Numeric,
FieldMetadataType.Probability, FieldMetadataType.Probability,
FieldMetadataType.Uuid, FieldMetadataType.Uuid,
FieldMetadataType.Phone,
]; ];
return ( return (