mirror of
https://github.com/twentyhq/twenty.git
synced 2024-09-11 23:37:21 +03:00
feat: simplify field preview logic in Settings (#5541)
Closes #5382 TODO: - [x] Test all field previews in app - [x] Fix tests - [x] Fix JSON preview
This commit is contained in:
parent
1ae7fbe90d
commit
c7d61e183a
@ -186,7 +186,7 @@
|
||||
"uuid": "^9.0.0",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"xlsx-ugnis": "^0.19.3",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { isString } from '@sniptt/guards';
|
||||
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||
@ -26,8 +28,11 @@ import { isFieldSelectValue } from '@/object-record/record-field/types/guards/is
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
const isValueEmpty = (value: unknown) => !isDefined(value) || value === '';
|
||||
const isValueEmpty = (value: unknown) =>
|
||||
!isDefined(value) ||
|
||||
(isString(value) && stripSimpleQuotesFromString(value) === '');
|
||||
|
||||
export const isFieldValueEmpty = ({
|
||||
fieldDefinition,
|
||||
@ -78,7 +83,8 @@ export const isFieldValueEmpty = ({
|
||||
if (isFieldFullName(fieldDefinition)) {
|
||||
return (
|
||||
!isFieldFullNameValue(fieldValue) ||
|
||||
isValueEmpty(fieldValue?.firstName + fieldValue?.lastName)
|
||||
(isValueEmpty(fieldValue?.firstName) &&
|
||||
isValueEmpty(fieldValue?.lastName))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import { currencyCodeSchema } from '@/object-record/record-field/validation-schemas/currencyCodeSchema';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
import { simpleQuotesStringSchema } from '~/utils/validation-schemas/simpleQuotesStringSchema';
|
||||
|
||||
export const currencyFieldDefaultValueSchema = z.object({
|
||||
amountMicros: z.number().nullable(),
|
||||
currencyCode: simpleQuotesStringSchema.refine(
|
||||
(value): value is `'${CurrencyCode}'` =>
|
||||
currencyCodeSchema.safeParse(stripSimpleQuotesFromString(value)).success,
|
||||
{ message: 'String is not a valid currencyCode' },
|
||||
),
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
import { simpleQuotesStringSchema } from '~/utils/validation-schemas/simpleQuotesStringSchema';
|
||||
|
||||
export const multiSelectFieldDefaultValueSchema = (
|
||||
options?: FieldMetadataItemOption[],
|
||||
) => {
|
||||
if (!options?.length) return z.array(simpleQuotesStringSchema).nullable();
|
||||
|
||||
const optionValues = options.map(({ value }) => value);
|
||||
|
||||
return z
|
||||
.array(
|
||||
simpleQuotesStringSchema.refine(
|
||||
(value) => optionValues.includes(stripSimpleQuotesFromString(value)),
|
||||
{
|
||||
message: `String is not a valid multi-select option, available options are: ${options.join(
|
||||
', ',
|
||||
)}`,
|
||||
},
|
||||
),
|
||||
)
|
||||
.nullable();
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
import { simpleQuotesStringSchema } from '~/utils/validation-schemas/simpleQuotesStringSchema';
|
||||
|
||||
export const selectFieldDefaultValueSchema = (
|
||||
options?: FieldMetadataItemOption[],
|
||||
) => {
|
||||
if (!options?.length) return simpleQuotesStringSchema.nullable();
|
||||
|
||||
const optionValues = options.map(({ value }) => value);
|
||||
|
||||
return simpleQuotesStringSchema
|
||||
.refine(
|
||||
(value) => optionValues.includes(stripSimpleQuotesFromString(value)),
|
||||
{
|
||||
message: `String is not a valid select option, available options are: ${options.join(
|
||||
', ',
|
||||
)}`,
|
||||
},
|
||||
)
|
||||
.nullable();
|
||||
};
|
@ -32,10 +32,7 @@ export type SettingsFieldTypeConfig = {
|
||||
defaultValue?: unknown;
|
||||
};
|
||||
|
||||
export const SETTINGS_FIELD_TYPE_CONFIGS: Record<
|
||||
SettingsSupportedFieldType,
|
||||
SettingsFieldTypeConfig
|
||||
> = {
|
||||
export const SETTINGS_FIELD_TYPE_CONFIGS = {
|
||||
[FieldMetadataType.Uuid]: {
|
||||
label: 'Unique ID',
|
||||
Icon: IconKey,
|
||||
@ -137,6 +134,9 @@ export const SETTINGS_FIELD_TYPE_CONFIGS: Record<
|
||||
[FieldMetadataType.RawJson]: {
|
||||
label: 'JSON',
|
||||
Icon: IconJson,
|
||||
defaultValue: `{ "key": "value" }`,
|
||||
defaultValue: { key: 'value' },
|
||||
},
|
||||
};
|
||||
} as const satisfies Record<
|
||||
SettingsSupportedFieldType,
|
||||
SettingsFieldTypeConfig
|
||||
>;
|
||||
|
@ -80,21 +80,22 @@ const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
||||
`;
|
||||
|
||||
const previewableTypes = [
|
||||
FieldMetadataType.Address,
|
||||
FieldMetadataType.Boolean,
|
||||
FieldMetadataType.Currency,
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.Date,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.MultiSelect,
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.FullName,
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.Links,
|
||||
FieldMetadataType.MultiSelect,
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Text,
|
||||
FieldMetadataType.Address,
|
||||
FieldMetadataType.RawJson,
|
||||
FieldMetadataType.Phone,
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.RawJson,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.Text,
|
||||
];
|
||||
|
||||
export const SettingsDataModelFieldSettingsFormCard = ({
|
||||
|
@ -2,25 +2,15 @@ import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { currencyCodeSchema } from '@/object-record/record-field/validation-schemas/currencyCodeSchema';
|
||||
import { currencyFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/currencyFieldDefaultValueSchema';
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
import { simpleQuotesStringSchema } from '~/utils/validation-schemas/simpleQuotesStringSchema';
|
||||
|
||||
export const settingsDataModelFieldCurrencyFormSchema = z.object({
|
||||
defaultValue: z.object({
|
||||
amountMicros: z.number().nullable(),
|
||||
currencyCode: simpleQuotesStringSchema.refine(
|
||||
(value) =>
|
||||
currencyCodeSchema.safeParse(stripSimpleQuotesFromString(value))
|
||||
.success,
|
||||
{ message: 'String is not a valid currencyCode' },
|
||||
),
|
||||
}),
|
||||
defaultValue: currencyFieldDefaultValueSchema,
|
||||
});
|
||||
|
||||
export type SettingsDataModelFieldCurrencyFormValues = z.infer<
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
FieldMetadataItemOption,
|
||||
} from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { selectOptionsSchema } from '@/object-metadata/validation-schemas/selectOptionsSchema';
|
||||
import { multiSelectFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/multiSelectFieldDefaultValueSchema';
|
||||
import { selectFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/selectFieldDefaultValueSchema';
|
||||
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
|
||||
import { generateNewSelectOption } from '@/settings/data-model/fields/forms/select/utils/generateNewSelectOption';
|
||||
import { isSelectOptionDefaultValue } from '@/settings/data-model/utils/isSelectOptionDefaultValue';
|
||||
@ -21,17 +23,16 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
import { toSpliced } from '~/utils/array/toSpliced';
|
||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||
import { simpleQuotesStringSchema } from '~/utils/validation-schemas/simpleQuotesStringSchema';
|
||||
|
||||
import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow';
|
||||
|
||||
export const settingsDataModelFieldSelectFormSchema = z.object({
|
||||
defaultValue: simpleQuotesStringSchema.nullable(),
|
||||
defaultValue: selectFieldDefaultValueSchema(),
|
||||
options: selectOptionsSchema,
|
||||
});
|
||||
|
||||
export const settingsDataModelFieldMultiSelectFormSchema = z.object({
|
||||
defaultValue: z.array(simpleQuotesStringSchema).nullable(),
|
||||
defaultValue: multiSelectFieldDefaultValueSchema(),
|
||||
options: selectOptionsSchema,
|
||||
});
|
||||
|
||||
|
@ -4,13 +4,15 @@ import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { FieldDisplay } from '@/object-record/record-field/components/FieldDisplay';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { BooleanFieldInput } from '@/object-record/record-field/meta-types/input/components/BooleanFieldInput';
|
||||
import { RatingFieldInput } from '@/object-record/record-field/meta-types/input/components/RatingFieldInput';
|
||||
import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetFieldValueEffect';
|
||||
import { SettingsDataModelSetRecordEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetRecordEffect';
|
||||
import { useFieldPreview } from '@/settings/data-model/fields/preview/hooks/useFieldPreview';
|
||||
import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue';
|
||||
import { usePreviewRecord } from '@/settings/data-model/fields/preview/hooks/usePreviewRecord';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export type SettingsDataModelFieldPreviewProps = {
|
||||
@ -61,17 +63,40 @@ export const SettingsDataModelFieldPreview = ({
|
||||
const { getIcon } = useIcons();
|
||||
const FieldIcon = getIcon(fieldMetadataItem.icon);
|
||||
|
||||
const { entityId, fieldName, fieldPreviewValue, isLabelIdentifier, record } =
|
||||
useFieldPreview({
|
||||
fieldMetadataItem,
|
||||
// id and name are undefined in create mode (field does not exist yet)
|
||||
// and defined in edit mode.
|
||||
const isLabelIdentifier =
|
||||
!!fieldMetadataItem.id &&
|
||||
!!fieldMetadataItem.name &&
|
||||
isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: fieldMetadataItem.id,
|
||||
name: fieldMetadataItem.name,
|
||||
},
|
||||
objectMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
const previewRecord = usePreviewRecord({
|
||||
objectMetadataItem,
|
||||
skip: !isLabelIdentifier,
|
||||
});
|
||||
|
||||
const fieldPreviewValue = useFieldPreviewValue({
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
skip: isLabelIdentifier,
|
||||
});
|
||||
|
||||
const fieldName =
|
||||
fieldMetadataItem.name || `${fieldMetadataItem.type}-new-field`;
|
||||
const entityId =
|
||||
previewRecord?.id ??
|
||||
`${objectMetadataItem.nameSingular}-${fieldName}-preview`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{record ? (
|
||||
<SettingsDataModelSetRecordEffect record={record} />
|
||||
{previewRecord ? (
|
||||
<SettingsDataModelSetRecordEffect record={previewRecord} />
|
||||
) : (
|
||||
<SettingsDataModelSetFieldValueEffect
|
||||
entityId={entityId}
|
||||
|
@ -8,6 +8,7 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedOpportunityObjectMetadataItem,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
@ -24,10 +25,7 @@ const meta: Meta<typeof SettingsDataModelFieldPreviewCard> = {
|
||||
SnackBarDecorator,
|
||||
],
|
||||
args: {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.Text,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
objectMetadataItem: mockedPersonObjectMetadataItem,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 480 },
|
||||
@ -38,21 +36,41 @@ const meta: Meta<typeof SettingsDataModelFieldPreviewCard> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsDataModelFieldPreviewCard>;
|
||||
|
||||
export const Text: Story = {};
|
||||
export const LabelIdentifier: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name, type }) =>
|
||||
name === 'name' && type === FieldMetadataType.FullName,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Text: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name, type }) => name === 'city' && type === FieldMetadataType.Text,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Boolean: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.Boolean,
|
||||
({ name, type }) =>
|
||||
name === 'idealCustomerProfile' && type === FieldMetadataType.Boolean,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
export const Currency: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.Currency,
|
||||
({ name, type }) =>
|
||||
name === 'annualRecurringRevenue' &&
|
||||
type === FieldMetadataType.Currency,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
@ -61,14 +79,27 @@ export const Date: Story = {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.DateTime,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
export const Link: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.Link,
|
||||
({ name, type }) =>
|
||||
name === 'linkedinLink' && type === FieldMetadataType.Link,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
export const Links: Story = {
|
||||
args: {
|
||||
...Link.args,
|
||||
fieldMetadataItem: {
|
||||
...Link.args!.fieldMetadataItem!,
|
||||
type: FieldMetadataType.Links,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -77,6 +108,7 @@ export const Number: Story = {
|
||||
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type === FieldMetadataType.Number,
|
||||
),
|
||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
@ -95,7 +127,27 @@ export const Relation: Story = {
|
||||
fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'company',
|
||||
),
|
||||
objectMetadataItem: mockedPersonObjectMetadataItem,
|
||||
relationObjectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
export const Select: Story = {
|
||||
args: {
|
||||
fieldMetadataItem: mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name, type }) => name === 'stage' && type === FieldMetadataType.Select,
|
||||
),
|
||||
objectMetadataItem: mockedOpportunityObjectMetadataItem,
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiSelect: Story = {
|
||||
args: {
|
||||
...Select.args,
|
||||
fieldMetadataItem: {
|
||||
...Select.args!.fieldMetadataItem!,
|
||||
defaultValue: null,
|
||||
label: 'Stages',
|
||||
type: FieldMetadataType.MultiSelect,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,67 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata';
|
||||
|
||||
import { useFieldPreview } from '../useFieldPreview';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MockedProvider addTypename={false}>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
{children}
|
||||
</SnackBarProviderScope>
|
||||
</MockedProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
describe('useFieldPreview', () => {
|
||||
it('returns default preview data if no records are found', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'linkedinLink',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreview({ fieldMetadataItem, objectMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual({
|
||||
entityId: 'company-linkedinLink-preview-field-form',
|
||||
fieldName: 'linkedinLink',
|
||||
fieldPreviewValue: { label: '', url: 'www.twenty.com' },
|
||||
isLabelIdentifier: false,
|
||||
record: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns default preview data for a label identifier field if no records are found', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'name',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreview({ fieldMetadataItem, objectMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual({
|
||||
entityId: 'company-name-preview-field-form',
|
||||
fieldName: 'name',
|
||||
fieldPreviewValue: 'Company',
|
||||
isLabelIdentifier: true,
|
||||
record: null,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,189 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedOpportunityObjectMetadataItem,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { useFieldPreviewValue } from '../useFieldPreviewValue';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MockedProvider>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<ObjectMetadataItemsProvider>{children}</ObjectMetadataItemsProvider>
|
||||
</SnackBarProviderScope>
|
||||
</MockedProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
describe('useFieldPreviewValue', () => {
|
||||
it('returns null if skip is true', () => {
|
||||
// Given
|
||||
const fieldName = 'amount';
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name, type }) =>
|
||||
name === fieldName && type === FieldMetadataType.Currency,
|
||||
);
|
||||
const skip = true;
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field ${fieldName} not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreviewValue({ fieldMetadataItem, skip }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the field's preview value for a Currency field", () => {
|
||||
// Given
|
||||
const fieldName = 'amount';
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name, type }) =>
|
||||
name === fieldName && type === FieldMetadataType.Currency,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field ${fieldName} not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreviewValue({ fieldMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual({
|
||||
amountMicros: 2000000000,
|
||||
currencyCode: 'USD',
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the relation object's label identifier preview value for a Relation field", () => {
|
||||
// Given
|
||||
const fieldMetadataItem = {
|
||||
name: 'people',
|
||||
type: FieldMetadataType.Relation,
|
||||
};
|
||||
const relationObjectMetadataItem = mockedPersonObjectMetadataItem;
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useFieldPreviewValue({
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual({
|
||||
__typename: 'Person',
|
||||
id: '',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the field's preview value for a Select field", () => {
|
||||
// Given
|
||||
const fieldName = 'stage';
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name, type }) =>
|
||||
name === fieldName && type === FieldMetadataType.Select,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field ${fieldName} not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreviewValue({ fieldMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toBe('NEW');
|
||||
});
|
||||
|
||||
it("returns the field's preview value for a Multi-Select field", () => {
|
||||
// Given
|
||||
const options: FieldMetadataItemOption[] = [
|
||||
{
|
||||
color: 'blue',
|
||||
label: 'Blue',
|
||||
value: 'BLUE',
|
||||
id: '1',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
label: 'Red',
|
||||
value: 'RED',
|
||||
id: '2',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
label: 'Green',
|
||||
value: 'GREEN',
|
||||
id: '3',
|
||||
position: 2,
|
||||
},
|
||||
];
|
||||
const fieldMetadataItem = {
|
||||
name: 'industry',
|
||||
type: FieldMetadataType.MultiSelect,
|
||||
options,
|
||||
};
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreviewValue({ fieldMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual(options.map(({ value }) => value));
|
||||
});
|
||||
|
||||
it("returns the field's preview value for other field types", () => {
|
||||
// Given
|
||||
const fieldName = 'employees';
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field ${fieldName} not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const { result } = renderHook(
|
||||
() => useFieldPreviewValue({ fieldMetadataItem }),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toBe(2000);
|
||||
});
|
||||
});
|
@ -1,106 +0,0 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||
import { getFieldDefaultPreviewValue } from '@/settings/data-model/utils/getFieldDefaultPreviewValue';
|
||||
import { getFieldPreviewValueFromRecord } from '@/settings/data-model/utils/getFieldPreviewValueFromRecord';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type UseFieldPreviewParams = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'icon' | 'type' | 'options' | 'defaultValue'
|
||||
> & {
|
||||
// id and name are undefined in create mode (field does not exist yet)
|
||||
// and are defined in edit mode.
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
relationObjectMetadataItem?: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const useFieldPreview = ({
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
}: UseFieldPreviewParams) => {
|
||||
const isLabelIdentifier =
|
||||
!!fieldMetadataItem.id &&
|
||||
!!fieldMetadataItem.name &&
|
||||
isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: fieldMetadataItem.id,
|
||||
name: fieldMetadataItem.name,
|
||||
},
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const { records } = useFindManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
limit: 1,
|
||||
skip: !fieldMetadataItem.name,
|
||||
orderBy: {
|
||||
[fieldMetadataItem.name ?? '']: 'AscNullsLast',
|
||||
},
|
||||
});
|
||||
const [firstRecord] = records;
|
||||
|
||||
const fieldPreviewValueFromFirstRecord =
|
||||
firstRecord && fieldMetadataItem.name
|
||||
? getFieldPreviewValueFromRecord({
|
||||
record: firstRecord,
|
||||
fieldMetadataItem: {
|
||||
name: fieldMetadataItem.name,
|
||||
type: fieldMetadataItem.type,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
const isValueFromFirstRecord =
|
||||
firstRecord &&
|
||||
!isFieldValueEmpty({
|
||||
fieldDefinition: { type: fieldMetadataItem.type },
|
||||
fieldValue: fieldPreviewValueFromFirstRecord,
|
||||
selectOptionValues: fieldMetadataItem.options?.map(
|
||||
(option) => option.value,
|
||||
),
|
||||
});
|
||||
|
||||
const { records: relationRecords } = useFindManyRecords({
|
||||
objectNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular ||
|
||||
CoreObjectNameSingular.Company,
|
||||
limit: 1,
|
||||
skip:
|
||||
!relationObjectMetadataItem ||
|
||||
fieldMetadataItem.type !== FieldMetadataType.Relation ||
|
||||
isValueFromFirstRecord,
|
||||
});
|
||||
const [firstRelationRecord] = relationRecords;
|
||||
|
||||
const fieldPreviewValue = isValueFromFirstRecord
|
||||
? fieldPreviewValueFromFirstRecord
|
||||
: firstRelationRecord ??
|
||||
getFieldDefaultPreviewValue({
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
const fieldName =
|
||||
fieldMetadataItem.name || `${fieldMetadataItem.type}-new-field`;
|
||||
const entityId = isValueFromFirstRecord
|
||||
? firstRecord.id
|
||||
: `${objectMetadataItem.nameSingular}-${fieldMetadataItem.name}-preview-field-form`;
|
||||
|
||||
return {
|
||||
entityId,
|
||||
fieldName,
|
||||
fieldPreviewValue,
|
||||
isLabelIdentifier,
|
||||
record: isValueFromFirstRecord ? firstRecord : null,
|
||||
};
|
||||
};
|
@ -0,0 +1,51 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useRelationFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useRelationFieldPreviewValue';
|
||||
import { getCurrencyFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue';
|
||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||
import { getMultiSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getMultiSelectFieldPreviewValue';
|
||||
import { getSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getSelectFieldPreviewValue';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type UseFieldPreviewParams = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'type' | 'options' | 'defaultValue'
|
||||
>;
|
||||
relationObjectMetadataItem?: ObjectMetadataItem;
|
||||
skip?: boolean;
|
||||
};
|
||||
|
||||
export const useFieldPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
skip,
|
||||
}: UseFieldPreviewParams) => {
|
||||
const relationFieldPreviewValue = useRelationFieldPreviewValue({
|
||||
relationObjectMetadataItem: relationObjectMetadataItem ?? {
|
||||
fields: [],
|
||||
labelSingular: '',
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
},
|
||||
skip:
|
||||
skip ||
|
||||
fieldMetadataItem.type !== FieldMetadataType.Relation ||
|
||||
!relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
if (skip === true) return null;
|
||||
|
||||
switch (fieldMetadataItem.type) {
|
||||
case FieldMetadataType.Currency:
|
||||
return getCurrencyFieldPreviewValue({ fieldMetadataItem });
|
||||
case FieldMetadataType.Relation:
|
||||
return relationFieldPreviewValue;
|
||||
case FieldMetadataType.Select:
|
||||
return getSelectFieldPreviewValue({ fieldMetadataItem });
|
||||
case FieldMetadataType.MultiSelect:
|
||||
return getMultiSelectFieldPreviewValue({ fieldMetadataItem });
|
||||
default:
|
||||
return getFieldPreviewValue({ fieldMetadataItem });
|
||||
}
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { pascalCase } from '~/utils/string/pascalCase';
|
||||
|
||||
type UsePreviewRecordParams = {
|
||||
objectMetadataItem: Pick<
|
||||
ObjectMetadataItem,
|
||||
| 'fields'
|
||||
| 'labelIdentifierFieldMetadataId'
|
||||
| 'labelSingular'
|
||||
| 'nameSingular'
|
||||
>;
|
||||
skip?: boolean;
|
||||
};
|
||||
|
||||
export const usePreviewRecord = ({
|
||||
objectMetadataItem,
|
||||
skip: skipFromProps,
|
||||
}: UsePreviewRecordParams): ObjectRecord | null => {
|
||||
const labelIdentifierFieldMetadataItem =
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
||||
const skip = skipFromProps || !labelIdentifierFieldMetadataItem;
|
||||
|
||||
const { records } = useFindManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
limit: 1,
|
||||
skip,
|
||||
});
|
||||
|
||||
if (skip) return null;
|
||||
|
||||
const [firstRecord] = records;
|
||||
|
||||
if (
|
||||
isDefined(firstRecord) &&
|
||||
!isFieldValueEmpty({
|
||||
fieldDefinition: { type: labelIdentifierFieldMetadataItem.type },
|
||||
fieldValue: firstRecord?.[labelIdentifierFieldMetadataItem.name],
|
||||
})
|
||||
) {
|
||||
return firstRecord;
|
||||
}
|
||||
|
||||
const fieldPreviewValue =
|
||||
labelIdentifierFieldMetadataItem.type === FieldMetadataType.Text
|
||||
? objectMetadataItem.labelSingular
|
||||
: getFieldPreviewValue({
|
||||
fieldMetadataItem: labelIdentifierFieldMetadataItem,
|
||||
});
|
||||
|
||||
const placeholderRecord = {
|
||||
__typename: pascalCase(objectMetadataItem.nameSingular),
|
||||
id: '',
|
||||
[labelIdentifierFieldMetadataItem.name]: fieldPreviewValue,
|
||||
};
|
||||
|
||||
// If no record was found, or if the label identifier field value is empty, display a placeholder record
|
||||
return placeholderRecord;
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { usePreviewRecord } from '@/settings/data-model/fields/preview/hooks/usePreviewRecord';
|
||||
|
||||
type UseRelationFieldPreviewParams = {
|
||||
relationObjectMetadataItem: Pick<
|
||||
ObjectMetadataItem,
|
||||
| 'fields'
|
||||
| 'labelIdentifierFieldMetadataId'
|
||||
| 'labelSingular'
|
||||
| 'nameSingular'
|
||||
>;
|
||||
skip?: boolean;
|
||||
};
|
||||
|
||||
export const useRelationFieldPreviewValue = ({
|
||||
relationObjectMetadataItem,
|
||||
skip,
|
||||
}: UseRelationFieldPreviewParams) =>
|
||||
usePreviewRecord({
|
||||
objectMetadataItem: relationObjectMetadataItem,
|
||||
skip,
|
||||
});
|
@ -0,0 +1,126 @@
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedOpportunityObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { getCurrencyFieldPreviewValue } from '../getCurrencyFieldPreviewValue';
|
||||
|
||||
describe('getCurrencyFieldPreviewValue', () => {
|
||||
it('returns null if the field is not a Currency field', () => {
|
||||
// Given
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type !== FieldMetadataType.Currency,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error('Field not found');
|
||||
}
|
||||
|
||||
// When
|
||||
const previewValue = getCurrencyFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
|
||||
const fieldName = 'amount';
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name, type }) =>
|
||||
name === fieldName && type === FieldMetadataType.Currency,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
it("returns the parsed defaultValue if a valid defaultValue is found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = {
|
||||
amountMicros: 3000000000,
|
||||
currencyCode: `'${CurrencyCode.EUR}'`,
|
||||
};
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getCurrencyFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual({
|
||||
amountMicros: defaultValue.amountMicros,
|
||||
currencyCode: CurrencyCode.EUR,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a placeholder amountMicros if it is empty in the field's metadata defaultValue", () => {
|
||||
// Given
|
||||
const defaultValue = {
|
||||
amountMicros: null,
|
||||
currencyCode: `'${CurrencyCode.EUR}'`,
|
||||
};
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getCurrencyFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual({
|
||||
amountMicros: 2000000000,
|
||||
currencyCode: CurrencyCode.EUR,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a placeholder default value if the defaultValue found in the field's metadata is invalid", () => {
|
||||
// Given
|
||||
const defaultValue = {
|
||||
amountMicros: null,
|
||||
currencyCode: "''",
|
||||
};
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getCurrencyFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual({
|
||||
amountMicros: 2000000000,
|
||||
currencyCode: CurrencyCode.USD,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a placeholder default value if no defaultValue is found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = null;
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getCurrencyFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual({
|
||||
amountMicros: 2000000000,
|
||||
currencyCode: CurrencyCode.USD,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,85 @@
|
||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedCustomObjectMetadataItem,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
describe('getFieldPreviewValue', () => {
|
||||
it("returns the field's defaultValue from metadata if it exists", () => {
|
||||
// Given
|
||||
const fieldName = 'idealCustomerProfile';
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const result = getFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns a placeholder defaultValue if the field metadata does not have a defaultValue', () => {
|
||||
// Given
|
||||
const fieldName = 'employees';
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const result = getFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(result).toBe(2000);
|
||||
expect(result).toBe(
|
||||
getSettingsFieldTypeConfig(FieldMetadataType.Number)?.defaultValue,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null if the field is supported in Settings but has no pre-configured placeholder defaultValue', () => {
|
||||
// Given
|
||||
const fieldName = 'company';
|
||||
const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name }) => name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const result = getFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if the field is not supported in Settings', () => {
|
||||
// Given
|
||||
const fieldName = 'position';
|
||||
const fieldMetadataItem = mockedCustomObjectMetadataItem.fields.find(
|
||||
({ name }) => name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
// When
|
||||
const result = getFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
@ -0,0 +1,129 @@
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedCustomObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { getMultiSelectFieldPreviewValue } from '../getMultiSelectFieldPreviewValue';
|
||||
|
||||
describe('getMultiSelectFieldPreviewValue', () => {
|
||||
it('returns null if the field is not a Multi-Select field', () => {
|
||||
// Given
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type !== FieldMetadataType.MultiSelect,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error('Field not found');
|
||||
}
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
|
||||
const fieldName = 'priority';
|
||||
const selectFieldMetadataItem = mockedCustomObjectMetadataItem.fields.find(
|
||||
({ name, type }) => name === fieldName && type === FieldMetadataType.Select,
|
||||
);
|
||||
|
||||
if (!selectFieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
const fieldMetadataItem = {
|
||||
...selectFieldMetadataItem,
|
||||
type: FieldMetadataType.MultiSelect,
|
||||
};
|
||||
|
||||
it("returns the defaultValue as an option value if a valid defaultValue is found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = ["'MEDIUM'", "'LOW'"];
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual(['MEDIUM', 'LOW']);
|
||||
});
|
||||
|
||||
it("returns all option values if no defaultValue was found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = null;
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']);
|
||||
expect(previewValue).toEqual(
|
||||
fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the first option value if the defaultValue found in the field's metadata is invalid", () => {
|
||||
// Given
|
||||
const defaultValue = false;
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']);
|
||||
expect(previewValue).toEqual(
|
||||
fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null if options are not defined', () => {
|
||||
// Given
|
||||
const fieldMetadataItemWithNoOptions = {
|
||||
...fieldMetadataItem,
|
||||
options: undefined,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithNoOptions,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if options array is empty', () => {
|
||||
// Given
|
||||
const fieldMetadataItemWithEmptyOptions = {
|
||||
...fieldMetadataItem,
|
||||
options: [],
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getMultiSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithEmptyOptions,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
});
|
@ -0,0 +1,124 @@
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedCustomObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { getSelectFieldPreviewValue } from '../getSelectFieldPreviewValue';
|
||||
|
||||
describe('getSelectFieldPreviewValue', () => {
|
||||
it('returns null if the field is not a Select field', () => {
|
||||
// Given
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ type }) => type !== FieldMetadataType.Select,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error('Field not found');
|
||||
}
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({ fieldMetadataItem });
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
|
||||
const fieldName = 'priority';
|
||||
const fieldMetadataItem = mockedCustomObjectMetadataItem.fields.find(
|
||||
({ name, type }) => name === fieldName && type === FieldMetadataType.Select,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(`Field '${fieldName}' not found`);
|
||||
}
|
||||
|
||||
it("returns the defaultValue as an option value if a valid defaultValue is found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = "'MEDIUM'";
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBe('MEDIUM');
|
||||
});
|
||||
|
||||
it("returns the first option value if no defaultValue was found in the field's metadata", () => {
|
||||
// Given
|
||||
const defaultValue = null;
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBe('LOW');
|
||||
expect(previewValue).toBe(
|
||||
fieldMetadataItemWithDefaultValue.options?.[0]?.value,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the first option value if the defaultValue found in the field's metadata is invalid", () => {
|
||||
// Given
|
||||
const defaultValue = false;
|
||||
const fieldMetadataItemWithDefaultValue = {
|
||||
...fieldMetadataItem,
|
||||
defaultValue,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithDefaultValue,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBe('LOW');
|
||||
expect(previewValue).toBe(
|
||||
fieldMetadataItemWithDefaultValue.options?.[0]?.value,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null if options are not defined', () => {
|
||||
// Given
|
||||
const fieldMetadataItemWithNoOptions = {
|
||||
...fieldMetadataItem,
|
||||
options: undefined,
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithNoOptions,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if options array is empty', () => {
|
||||
// Given
|
||||
const fieldMetadataItemWithEmptyOptions = {
|
||||
...fieldMetadataItem,
|
||||
options: [],
|
||||
};
|
||||
|
||||
// When
|
||||
const previewValue = getSelectFieldPreviewValue({
|
||||
fieldMetadataItem: fieldMetadataItemWithEmptyOptions,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(previewValue).toBeNull();
|
||||
});
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { currencyFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/currencyFieldDefaultValueSchema';
|
||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
export const getCurrencyFieldPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'options' | 'type'
|
||||
>;
|
||||
}): FieldCurrencyValue | null => {
|
||||
if (fieldMetadataItem.type !== FieldMetadataType.Currency) return null;
|
||||
|
||||
const placeholderDefaultValue = getSettingsFieldTypeConfig(
|
||||
FieldMetadataType.Currency,
|
||||
).defaultValue;
|
||||
|
||||
return currencyFieldDefaultValueSchema
|
||||
.transform((value) => ({
|
||||
amountMicros: value.amountMicros || placeholderDefaultValue.amountMicros,
|
||||
currencyCode: stripSimpleQuotesFromString(
|
||||
value.currencyCode,
|
||||
) as CurrencyCode,
|
||||
}))
|
||||
.catch(placeholderDefaultValue)
|
||||
.parse(fieldMetadataItem.defaultValue);
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const getFieldPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'defaultValue'>;
|
||||
}) => {
|
||||
if (!isFieldTypeSupportedInSettings(fieldMetadataItem.type)) return null;
|
||||
|
||||
if (
|
||||
!isFieldValueEmpty({
|
||||
fieldDefinition: { type: fieldMetadataItem.type },
|
||||
fieldValue: fieldMetadataItem.defaultValue,
|
||||
})
|
||||
) {
|
||||
return fieldMetadataItem.defaultValue;
|
||||
}
|
||||
|
||||
const fieldTypeConfig = getSettingsFieldTypeConfig(fieldMetadataItem.type);
|
||||
|
||||
if (
|
||||
isDefined(fieldTypeConfig) &&
|
||||
'defaultValue' in fieldTypeConfig &&
|
||||
isDefined(fieldTypeConfig.defaultValue)
|
||||
) {
|
||||
return fieldTypeConfig.defaultValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { multiSelectFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/multiSelectFieldDefaultValueSchema';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
export const getMultiSelectFieldPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'options' | 'type'
|
||||
>;
|
||||
}): FieldMultiSelectValue => {
|
||||
if (
|
||||
fieldMetadataItem.type !== FieldMetadataType.MultiSelect ||
|
||||
!fieldMetadataItem.options?.length
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allOptionValues = fieldMetadataItem.options.map(({ value }) => value);
|
||||
|
||||
return multiSelectFieldDefaultValueSchema(fieldMetadataItem.options)
|
||||
.refine(isDefined)
|
||||
.transform((value) =>
|
||||
value.map(stripSimpleQuotesFromString).filter(isNonEmptyString),
|
||||
)
|
||||
.refine(isNonEmptyArray)
|
||||
.catch(allOptionValues)
|
||||
.parse(fieldMetadataItem.defaultValue);
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldSelectValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { selectFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/selectFieldDefaultValueSchema';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
export const getSelectFieldPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'options' | 'type'
|
||||
>;
|
||||
}): FieldSelectValue => {
|
||||
if (
|
||||
fieldMetadataItem.type !== FieldMetadataType.Select ||
|
||||
!fieldMetadataItem.options?.length
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstOptionValue = fieldMetadataItem.options[0].value;
|
||||
|
||||
return selectFieldDefaultValueSchema(fieldMetadataItem.options)
|
||||
.refine(isDefined)
|
||||
.transform(stripSimpleQuotesFromString)
|
||||
.refine(isNonEmptyString)
|
||||
.catch(firstOptionValue)
|
||||
.parse(fieldMetadataItem.defaultValue);
|
||||
};
|
@ -1,176 +0,0 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
mockedCompanyObjectMetadataItem,
|
||||
mockedOpportunityObjectMetadataItem,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { getFieldDefaultPreviewValue } from '../getFieldDefaultPreviewValue';
|
||||
|
||||
describe('getFieldDefaultPreviewValue', () => {
|
||||
describe('SELECT field', () => {
|
||||
it('returns the default select option value', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedOpportunityObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'stage',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem: { ...fieldMetadataItem, defaultValue: "'MEETING'" },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual('MEETING');
|
||||
});
|
||||
|
||||
it('returns the first select option if no default option was found', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedOpportunityObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'stage',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem: { ...fieldMetadataItem, defaultValue: null },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual(fieldMetadataItem.options![0].value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RELATION field', () => {
|
||||
it('returns a record with a default label identifier (if relation label identifier type !== TEXT)', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'people',
|
||||
)!;
|
||||
const relationObjectMetadataItem = mockedPersonObjectMetadataItem;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a record with the relation object label singular as label identifier (if relation label identifier type === TEXT)', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedPersonObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'company',
|
||||
)!;
|
||||
const relationObjectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
name: 'Company',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null if the relation object does not have a label identifier field', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedPersonObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'company',
|
||||
)!;
|
||||
const relationObjectMetadataItem: ObjectMetadataItem = {
|
||||
...mockedCompanyObjectMetadataItem,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
fields: mockedCompanyObjectMetadataItem.fields.filter(
|
||||
({ name }) => name !== 'name',
|
||||
),
|
||||
};
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other fields', () => {
|
||||
it('returns the object singular name as default value for the label identifier field (type TEXT)', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'name',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toBe('Company');
|
||||
});
|
||||
|
||||
it('returns a default value for the label identifier field (type FULL_NAME)', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedPersonObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'name',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a default value for other field types', () => {
|
||||
// Given
|
||||
const objectMetadataItem = mockedCompanyObjectMetadataItem;
|
||||
const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
|
||||
({ name }) => name === 'domainName',
|
||||
)!;
|
||||
|
||||
// When
|
||||
const result = getFieldDefaultPreviewValue({
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toBe(
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,89 +0,0 @@
|
||||
import { isString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
export const getFieldDefaultPreviewValue = ({
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'type' | 'defaultValue' | 'options'
|
||||
> & {
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
relationObjectMetadataItem?: ObjectMetadataItem;
|
||||
}) => {
|
||||
if (fieldMetadataItem.type === FieldMetadataType.Select) {
|
||||
const defaultValue = isString(fieldMetadataItem.defaultValue)
|
||||
? stripSimpleQuotesFromString(fieldMetadataItem.defaultValue)
|
||||
: null;
|
||||
return defaultValue ?? fieldMetadataItem.options?.[0]?.value ?? null;
|
||||
}
|
||||
|
||||
if (fieldMetadataItem.type === FieldMetadataType.MultiSelect) {
|
||||
const defaultValues = Array.isArray(fieldMetadataItem.defaultValue)
|
||||
? fieldMetadataItem.defaultValue?.map((defaultValue: `'${string}'`) =>
|
||||
stripSimpleQuotesFromString(defaultValue),
|
||||
)
|
||||
: null;
|
||||
return defaultValues?.length
|
||||
? defaultValues
|
||||
: fieldMetadataItem.options?.map(({ value }) => value) ?? null;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
isDefined(relationObjectMetadataItem)
|
||||
) {
|
||||
const relationLabelIdentifierFieldMetadataItem =
|
||||
getLabelIdentifierFieldMetadataItem(relationObjectMetadataItem);
|
||||
|
||||
if (!relationLabelIdentifierFieldMetadataItem) return null;
|
||||
|
||||
const { type: relationLabelIdentifierFieldType } =
|
||||
relationLabelIdentifierFieldMetadataItem;
|
||||
const relationFieldTypeConfig = getSettingsFieldTypeConfig(
|
||||
relationLabelIdentifierFieldType,
|
||||
);
|
||||
|
||||
const defaultRelationLabelIdentifierFieldValue =
|
||||
relationLabelIdentifierFieldType === FieldMetadataType.Text
|
||||
? relationObjectMetadataItem.labelSingular
|
||||
: relationFieldTypeConfig?.defaultValue;
|
||||
|
||||
const defaultRelationRecord = {
|
||||
[relationLabelIdentifierFieldMetadataItem.name]:
|
||||
defaultRelationLabelIdentifierFieldValue,
|
||||
};
|
||||
|
||||
return defaultRelationRecord;
|
||||
}
|
||||
|
||||
const isLabelIdentifier =
|
||||
!!fieldMetadataItem.id &&
|
||||
!!fieldMetadataItem.name &&
|
||||
isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: fieldMetadataItem.id,
|
||||
name: fieldMetadataItem.name,
|
||||
},
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const fieldTypeConfig = getSettingsFieldTypeConfig(fieldMetadataItem.type);
|
||||
|
||||
return isLabelIdentifier && fieldMetadataItem.type === FieldMetadataType.Text
|
||||
? objectMetadataItem.labelSingular
|
||||
: fieldTypeConfig?.defaultValue;
|
||||
};
|
@ -1,8 +1,13 @@
|
||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
||||
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getSettingsFieldTypeConfig = (fieldType: FieldMetadataType) =>
|
||||
isFieldTypeSupportedInSettings(fieldType)
|
||||
export const getSettingsFieldTypeConfig = <T extends FieldMetadataType>(
|
||||
fieldType: T,
|
||||
) =>
|
||||
(isFieldTypeSupportedInSettings(fieldType)
|
||||
? SETTINGS_FIELD_TYPE_CONFIGS[fieldType]
|
||||
: undefined) as T extends SettingsSupportedFieldType
|
||||
? (typeof SETTINGS_FIELD_TYPE_CONFIGS)[T]
|
||||
: undefined;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { id } from 'date-fns/locale';
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectEdge,
|
||||
@ -3787,10 +3787,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
||||
isNullable: true,
|
||||
createdAt: '2024-04-08T12:48:49.538Z',
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
},
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
@ -3876,10 +3873,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
||||
isNullable: true,
|
||||
createdAt: '2024-04-08T12:48:49.538Z',
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: {
|
||||
url: "''",
|
||||
label: "''",
|
||||
},
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
@ -10562,7 +10556,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: {
|
||||
amountMicros: null,
|
||||
currencyCode: "''",
|
||||
currencyCode: `'${CurrencyCode.USD}'`,
|
||||
},
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
@ -10822,10 +10816,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
||||
isNullable: true,
|
||||
createdAt: '2024-04-08T12:48:49.538Z',
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: {
|
||||
url: "''",
|
||||
label: "''",
|
||||
},
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectEdge,
|
||||
ObjectMetadataItemsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@ -237,6 +238,48 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'fieldEdge',
|
||||
node: {
|
||||
__typename: 'field',
|
||||
id: 'e07fcc3f-beec-4d91-8488-9d1d2cfa5f99',
|
||||
type: FieldMetadataType.Select,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
description: 'A custom Select example',
|
||||
icon: 'IconWarning',
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
options: [
|
||||
{
|
||||
id: '2b98dc02-0d99-4f3e-890e-e2e6b8f3196c',
|
||||
value: 'LOW',
|
||||
label: 'Low',
|
||||
color: 'turquoise',
|
||||
},
|
||||
{
|
||||
id: 'd925a8de-d8ec-4b59-a079-64f4012e3311',
|
||||
value: 'MEDIUM',
|
||||
label: 'Medium',
|
||||
color: 'yellow',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
value: 'HIGH',
|
||||
label: 'High',
|
||||
color: 'red',
|
||||
},
|
||||
],
|
||||
isNullable: true,
|
||||
createdAt: '2024-04-08T12:48:49.538Z',
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -8,6 +8,8 @@ import { logError } from './logError';
|
||||
export const DEFAULT_DATE_LOCALE = 'en-EN';
|
||||
|
||||
export const parseDate = (dateToParse: Date | string | number) => {
|
||||
if (dateToParse === 'now') return DateTime.fromJSDate(new Date());
|
||||
|
||||
let formattedDate: DateTime | null = null;
|
||||
|
||||
if (!dateToParse) {
|
||||
|
11
yarn.lock
11
yarn.lock
@ -46640,7 +46640,7 @@ __metadata:
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:1.4.0"
|
||||
xlsx-ugnis: "npm:^0.19.3"
|
||||
zod: "npm:^3.22.2"
|
||||
zod: "npm:3.23.8"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -49717,7 +49717,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.20.2, zod@npm:^3.22.2":
|
||||
"zod@npm:3.23.8":
|
||||
version: 3.23.8
|
||||
resolution: "zod@npm:3.23.8"
|
||||
checksum: 8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.20.2":
|
||||
version: 3.22.4
|
||||
resolution: "zod@npm:3.22.4"
|
||||
checksum: 7578ab283dac0eee66a0ad0fc4a7f28c43e6745aadb3a529f59a4b851aa10872b3890398b3160f257f4b6817b4ce643debdda4fb21a2c040adda7862cab0a587
|
||||
|
Loading…
Reference in New Issue
Block a user