Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58cd7f7c05 | |||
| 6ada4eb88c | |||
| c69f34213a | |||
| 1308e11232 | |||
| a2830d8817 |
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type ApolloCache, type FetchResult } from '@apollo/client';
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
@@ -28,6 +29,13 @@ import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/ut
|
||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||
import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent';
|
||||
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
|
||||
import {
|
||||
getRecordInputPlaceholdersForRequiredFields,
|
||||
getRequiredFieldsUnfillableErrorMessage,
|
||||
getRequiredRelationFieldsMissingErrorMessage,
|
||||
REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE,
|
||||
REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE,
|
||||
} from '@/object-record/utils/getRecordInputPlaceholdersForRequiredFields';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
@@ -86,10 +94,44 @@ export const useCreateOneRecord = <
|
||||
|
||||
const idForCreation = recordInput.id ?? v4();
|
||||
|
||||
const {
|
||||
placeholders,
|
||||
missingRequiredRelationFields,
|
||||
missingRequiredFieldsUnfillable,
|
||||
} = getRecordInputPlaceholdersForRequiredFields(
|
||||
objectMetadataItem,
|
||||
recordInput,
|
||||
);
|
||||
|
||||
if (missingRequiredRelationFields.length > 0) {
|
||||
setLoading(false);
|
||||
throw new CustomError(
|
||||
getRequiredRelationFieldsMissingErrorMessage(
|
||||
missingRequiredRelationFields,
|
||||
),
|
||||
REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE,
|
||||
);
|
||||
}
|
||||
|
||||
if (missingRequiredFieldsUnfillable.length > 0) {
|
||||
setLoading(false);
|
||||
throw new CustomError(
|
||||
getRequiredFieldsUnfillableErrorMessage(
|
||||
missingRequiredFieldsUnfillable,
|
||||
),
|
||||
REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE,
|
||||
);
|
||||
}
|
||||
|
||||
const recordInputWithPlaceholder = {
|
||||
...placeholders,
|
||||
...recordInput,
|
||||
};
|
||||
|
||||
const sanitizedInput = {
|
||||
...sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput,
|
||||
recordInput: recordInputWithPlaceholder,
|
||||
}),
|
||||
id: idForCreation,
|
||||
};
|
||||
@@ -101,7 +143,7 @@ export const useCreateOneRecord = <
|
||||
objectMetadataItems,
|
||||
recordInput: {
|
||||
...computeOptimisticCreateRecordBaseRecordInput(objectMetadataItem),
|
||||
...recordInput,
|
||||
...recordInputWithPlaceholder,
|
||||
id: idForCreation,
|
||||
},
|
||||
objectPermissionsByObjectMetadataId,
|
||||
@@ -143,7 +185,8 @@ export const useCreateOneRecord = <
|
||||
variables: {
|
||||
input: sanitizedInput,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
update: (cache: ApolloCache<object>, result: FetchResult) => {
|
||||
const { data } = result;
|
||||
const record = data?.[mutationResponseField];
|
||||
if (skipPostOptimisticEffect === false && isDefined(record)) {
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
@@ -162,6 +205,8 @@ export const useCreateOneRecord = <
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
setLoading(false);
|
||||
|
||||
if (!isDefined(recordCreatedInCache)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
import {
|
||||
getRecordInputPlaceholdersForRequiredFields,
|
||||
getRequiredFieldsUnfillableErrorMessage,
|
||||
getRequiredRelationFieldsMissingErrorMessage,
|
||||
REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE,
|
||||
REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE,
|
||||
} from '@/object-record/utils/getRecordInputPlaceholdersForRequiredFields';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
const createMockObjectMetadataItem = (
|
||||
overrides: Partial<ObjectMetadataItem> & {
|
||||
fields: ObjectMetadataItem['fields'];
|
||||
},
|
||||
): ObjectMetadataItem =>
|
||||
({
|
||||
nameSingular: 'playbook',
|
||||
labelSingular: 'Playbook',
|
||||
labelPlural: 'Playbooks',
|
||||
fields: [],
|
||||
...overrides,
|
||||
}) as ObjectMetadataItem;
|
||||
|
||||
const createMockField = (
|
||||
overrides: Partial<FieldMetadataItem>,
|
||||
): FieldMetadataItem =>
|
||||
({
|
||||
id: 'field-id',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
...overrides,
|
||||
}) as FieldMetadataItem;
|
||||
|
||||
describe('getRecordInputPlaceholdersForRequiredFields', () => {
|
||||
it('should add placeholder for missing required TEXT label identifier', () => {
|
||||
const objectMetadataItem = createMockObjectMetadataItem({
|
||||
labelIdentifierFieldMetadataId: 'title-field-id',
|
||||
fields: [
|
||||
createMockField({
|
||||
id: 'title-field-id',
|
||||
name: 'title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { placeholders, missingRequiredRelationFields } =
|
||||
getRecordInputPlaceholdersForRequiredFields(objectMetadataItem, {});
|
||||
|
||||
expect(placeholders.title).toBeDefined();
|
||||
expect(typeof placeholders.title).toBe('string');
|
||||
expect((placeholders.title as string).length).toBeGreaterThan(0);
|
||||
expect(missingRequiredRelationFields).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should skip placeholder when value already provided', () => {
|
||||
const objectMetadataItem = createMockObjectMetadataItem({
|
||||
labelIdentifierFieldMetadataId: 'title-field-id',
|
||||
fields: [
|
||||
createMockField({
|
||||
id: 'title-field-id',
|
||||
name: 'title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { placeholders } = getRecordInputPlaceholdersForRequiredFields(
|
||||
objectMetadataItem,
|
||||
{ title: 'My Playbook' },
|
||||
);
|
||||
|
||||
expect(placeholders.title).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add missing required relation to missingRequiredRelationFields', () => {
|
||||
const objectMetadataItem = createMockObjectMetadataItem({
|
||||
labelIdentifierFieldMetadataId: 'title-field-id',
|
||||
fields: [
|
||||
createMockField({
|
||||
id: 'title-field-id',
|
||||
name: 'title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
}),
|
||||
createMockField({
|
||||
id: 'workspace-field-id',
|
||||
name: 'workspace',
|
||||
label: 'Workspace',
|
||||
type: FieldMetadataType.RELATION,
|
||||
isNullable: false,
|
||||
relation: { type: RelationType.MANY_TO_ONE },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { placeholders, missingRequiredRelationFields } =
|
||||
getRecordInputPlaceholdersForRequiredFields(objectMetadataItem, {});
|
||||
|
||||
expect(placeholders.title).toBeDefined();
|
||||
expect(missingRequiredRelationFields).toHaveLength(1);
|
||||
expect(missingRequiredRelationFields[0].name).toBe('workspace');
|
||||
});
|
||||
|
||||
it('should treat relation as provided when {fieldName}Id is set', () => {
|
||||
const objectMetadataItem = createMockObjectMetadataItem({
|
||||
labelIdentifierFieldMetadataId: 'title-field-id',
|
||||
fields: [
|
||||
createMockField({
|
||||
id: 'title-field-id',
|
||||
name: 'title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
}),
|
||||
createMockField({
|
||||
id: 'workspace-field-id',
|
||||
name: 'workspace',
|
||||
label: 'Workspace',
|
||||
type: FieldMetadataType.RELATION,
|
||||
isNullable: false,
|
||||
relation: { type: RelationType.MANY_TO_ONE },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { missingRequiredRelationFields } =
|
||||
getRecordInputPlaceholdersForRequiredFields(objectMetadataItem, {
|
||||
workspaceId: 'existing-workspace-id',
|
||||
});
|
||||
|
||||
expect(missingRequiredRelationFields).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequiredRelationFieldsMissingErrorMessage', () => {
|
||||
it('should return formatted message with field labels', () => {
|
||||
const fields = [
|
||||
createMockField({
|
||||
name: 'workspace',
|
||||
label: 'Workspace',
|
||||
type: FieldMetadataType.RELATION,
|
||||
}),
|
||||
createMockField({
|
||||
name: 'owner',
|
||||
label: 'Owner',
|
||||
type: FieldMetadataType.RELATION,
|
||||
}),
|
||||
];
|
||||
|
||||
const message = getRequiredRelationFieldsMissingErrorMessage(fields);
|
||||
|
||||
expect(message).toContain('Workspace');
|
||||
expect(message).toContain('Owner');
|
||||
expect(message).toContain('required but cannot be auto-filled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE', () => {
|
||||
it('should be the expected constant', () => {
|
||||
expect(REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE).toBe(
|
||||
'REQUIRED_RELATION_FIELDS_MISSING',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('missingRequiredFieldsUnfillable', () => {
|
||||
it('should add required SELECT with no options to missingRequiredFieldsUnfillable', () => {
|
||||
const objectMetadataItem = createMockObjectMetadataItem({
|
||||
labelIdentifierFieldMetadataId: 'title-field-id',
|
||||
fields: [
|
||||
createMockField({
|
||||
id: 'title-field-id',
|
||||
name: 'title',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: false,
|
||||
}),
|
||||
createMockField({
|
||||
id: 'status-field-id',
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
type: FieldMetadataType.SELECT,
|
||||
isNullable: false,
|
||||
options: [],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { placeholders, missingRequiredFieldsUnfillable } =
|
||||
getRecordInputPlaceholdersForRequiredFields(objectMetadataItem, {});
|
||||
|
||||
expect(placeholders.title).toBeDefined();
|
||||
expect(placeholders.status).toBeUndefined();
|
||||
expect(missingRequiredFieldsUnfillable).toHaveLength(1);
|
||||
expect(missingRequiredFieldsUnfillable[0].name).toBe('status');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequiredFieldsUnfillableErrorMessage', () => {
|
||||
it('should return formatted message', () => {
|
||||
const fields = [createMockField({ name: 'status', label: 'Status' })];
|
||||
const message = getRequiredFieldsUnfillableErrorMessage(fields);
|
||||
expect(message).toContain('Status');
|
||||
expect(message).toContain('cannot be auto-filled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE', () => {
|
||||
it('should be the expected constant', () => {
|
||||
expect(REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE).toBe(
|
||||
'REQUIRED_FIELDS_UNFILLABLE',
|
||||
);
|
||||
});
|
||||
});
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { format } from 'date-fns';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
const UNTITLED_PLACEHOLDER = 'Untitled';
|
||||
|
||||
// Unlike prefillRecord/generateEmptyFieldValue which produce empty/display values
|
||||
// (null, ''), this utility produces valid non-null placeholders for required
|
||||
// fields so the create mutation can succeed. The backend rejects null/empty
|
||||
// for required TEXT fields.
|
||||
//
|
||||
// TODO: Long-term product vision: when quick-create has no/minimal input, show
|
||||
// a creation form instead of auto-creating with placeholders, so users provide
|
||||
// real data rather than editing placeholder records after creation.
|
||||
|
||||
type GetRecordInputPlaceholdersForRequiredFieldsResult = {
|
||||
placeholders: Partial<ObjectRecord>;
|
||||
missingRequiredRelationFields: FieldMetadataItem[];
|
||||
missingRequiredFieldsUnfillable: FieldMetadataItem[];
|
||||
};
|
||||
|
||||
const getPlaceholderForFieldType = (
|
||||
field: FieldMetadataItem,
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
): unknown => {
|
||||
const { defaultValue } = field;
|
||||
const isLabelIdentifier =
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem)?.id === field.id;
|
||||
const untitledLabel = isLabelIdentifier
|
||||
? t`Untitled ${objectMetadataItem.labelSingular}`
|
||||
: UNTITLED_PLACEHOLDER;
|
||||
|
||||
if (field.type === FieldMetadataType.TEXT) {
|
||||
if (isNonEmptyString(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return untitledLabel;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.FULL_NAME) {
|
||||
const defaultFirstName =
|
||||
defaultValue &&
|
||||
typeof defaultValue === 'object' &&
|
||||
'firstName' in defaultValue
|
||||
? (defaultValue as { firstName?: string }).firstName
|
||||
: undefined;
|
||||
const firstName = isNonEmptyString(defaultFirstName)
|
||||
? defaultFirstName
|
||||
: untitledLabel;
|
||||
return { firstName, lastName: '' };
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.UUID) {
|
||||
return v4();
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.BOOLEAN) {
|
||||
return field.defaultValue ?? true;
|
||||
}
|
||||
|
||||
if (
|
||||
field.type === FieldMetadataType.NUMBER ||
|
||||
field.type === FieldMetadataType.NUMERIC ||
|
||||
field.type === FieldMetadataType.RATING ||
|
||||
field.type === FieldMetadataType.POSITION
|
||||
) {
|
||||
return defaultValue ?? 0;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.DATE) {
|
||||
return defaultValue ?? format(new Date(), 'yyyy-MM-dd');
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.DATE_TIME) {
|
||||
return defaultValue ?? new Date().toISOString();
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.SELECT) {
|
||||
const options = field.options?.map((opt) => opt.value) ?? [];
|
||||
if (isDefined(defaultValue) && options.includes(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return options[0] ?? undefined;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.MULTI_SELECT) {
|
||||
return defaultValue ?? [];
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.LINKS) {
|
||||
return {
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: untitledLabel,
|
||||
secondaryLinks: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.ADDRESS) {
|
||||
return {
|
||||
addressStreet1: untitledLabel,
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.EMAILS) {
|
||||
return {
|
||||
primaryEmail: 'untitled@example.com',
|
||||
additionalEmails: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.PHONES) {
|
||||
return {
|
||||
primaryPhoneNumber: '5551234567',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
additionalPhones: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RICH_TEXT_V2) {
|
||||
return {
|
||||
blocknote: null,
|
||||
markdown: untitledLabel,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.CURRENCY) {
|
||||
const defaultCurrency =
|
||||
defaultValue &&
|
||||
typeof defaultValue === 'object' &&
|
||||
'currencyCode' in defaultValue
|
||||
? (defaultValue as { amountMicros?: number; currencyCode?: string })
|
||||
: undefined;
|
||||
return {
|
||||
amountMicros: defaultCurrency?.amountMicros ?? 0,
|
||||
currencyCode: defaultCurrency?.currencyCode ?? 'USD',
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const canProvidePlaceholderForFieldType = (
|
||||
fieldType: FieldMetadataType,
|
||||
): boolean => {
|
||||
const placeholderableTypes: FieldMetadataType[] = [
|
||||
FieldMetadataType.TEXT,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
FieldMetadataType.UUID,
|
||||
FieldMetadataType.BOOLEAN,
|
||||
FieldMetadataType.NUMBER,
|
||||
FieldMetadataType.NUMERIC,
|
||||
FieldMetadataType.RATING,
|
||||
FieldMetadataType.POSITION,
|
||||
FieldMetadataType.DATE,
|
||||
FieldMetadataType.DATE_TIME,
|
||||
FieldMetadataType.SELECT,
|
||||
FieldMetadataType.MULTI_SELECT,
|
||||
FieldMetadataType.LINKS,
|
||||
FieldMetadataType.ADDRESS,
|
||||
FieldMetadataType.EMAILS,
|
||||
FieldMetadataType.PHONES,
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
FieldMetadataType.CURRENCY,
|
||||
];
|
||||
return placeholderableTypes.includes(fieldType);
|
||||
};
|
||||
|
||||
const isRequiredRelationField = (field: FieldMetadataItem): boolean =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
field.relation?.type === RelationType.MANY_TO_ONE &&
|
||||
field.isNullable === false;
|
||||
|
||||
const isRequiredMorphRelationField = (field: FieldMetadataItem): boolean =>
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
field.settings?.relationType === RelationType.MANY_TO_ONE &&
|
||||
field.isNullable === false;
|
||||
|
||||
export const getRecordInputPlaceholdersForRequiredFields = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
recordInput: Partial<ObjectRecord>,
|
||||
): GetRecordInputPlaceholdersForRequiredFieldsResult => {
|
||||
const placeholders: Partial<ObjectRecord> = {};
|
||||
const missingRequiredRelationFields: FieldMetadataItem[] = [];
|
||||
const missingRequiredFieldsUnfillable: FieldMetadataItem[] = [];
|
||||
|
||||
for (const field of objectMetadataItem.fields) {
|
||||
if (field.isNullable !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if value already provided (filters, RLS, or explicit input)
|
||||
const existingValue = recordInput[field.name];
|
||||
const relationJoinColumnName =
|
||||
isRequiredRelationField(field) || isRequiredMorphRelationField(field)
|
||||
? (getJoinColumnName(field.settings) ?? `${field.name}Id`)
|
||||
: undefined;
|
||||
const joinColumnValue = relationJoinColumnName
|
||||
? recordInput[relationJoinColumnName]
|
||||
: undefined;
|
||||
if (isDefined(existingValue) || isDefined(joinColumnValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRequiredRelationField(field) || isRequiredMorphRelationField(field)) {
|
||||
missingRequiredRelationFields.push(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!canProvidePlaceholderForFieldType(field.type)) {
|
||||
missingRequiredFieldsUnfillable.push(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
const placeholder = getPlaceholderForFieldType(field, objectMetadataItem);
|
||||
if (isDefined(placeholder)) {
|
||||
placeholders[field.name] = placeholder;
|
||||
} else {
|
||||
missingRequiredFieldsUnfillable.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
placeholders,
|
||||
missingRequiredRelationFields,
|
||||
missingRequiredFieldsUnfillable,
|
||||
};
|
||||
};
|
||||
|
||||
export const REQUIRED_RELATION_FIELDS_MISSING_ERROR_CODE =
|
||||
'REQUIRED_RELATION_FIELDS_MISSING' as const;
|
||||
|
||||
export const REQUIRED_FIELDS_UNFILLABLE_ERROR_CODE =
|
||||
'REQUIRED_FIELDS_UNFILLABLE' as const;
|
||||
|
||||
export const getRequiredRelationFieldsMissingErrorMessage = (
|
||||
missingRequiredRelationFields: FieldMetadataItem[],
|
||||
): string => {
|
||||
const fieldLabels = missingRequiredRelationFields
|
||||
.map((field) => field.label)
|
||||
.join(', ');
|
||||
return `Cannot create record: the field(s) "${fieldLabels}" are required but cannot be auto-filled. Make them optional in Settings > Data model, or add a default value.`;
|
||||
};
|
||||
|
||||
export const getRequiredFieldsUnfillableErrorMessage = (
|
||||
missingRequiredFieldsUnfillable: FieldMetadataItem[],
|
||||
): string => {
|
||||
const fieldLabels = missingRequiredFieldsUnfillable
|
||||
.map((field) => field.label)
|
||||
.join(', ');
|
||||
return `Cannot create record: the field(s) "${fieldLabels}" are required but cannot be auto-filled (e.g. SELECT with no options). Make them optional in Settings > Data model, or add a default value.`;
|
||||
};
|
||||
Reference in New Issue
Block a user