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:
@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user