Refactored all FieldDisplay types for performance optimization (#5768)
This PR is the second part of https://github.com/twentyhq/twenty/pull/5693. It optimizes all remaining field types. The observed improvements are : - x2 loading time improvement on table rows - more consistent render time Here's a summary of measured improvements, what's given here is the average of hundreds of renders with a React Profiler component. (in our Storybook performance stories) | Component | Before (µs) | After (µs) | | ----- | ------------- | --- | | TextFieldDisplay | 127 | 83 | | EmailFieldDisplay | 117 | 83 | | NumberFieldDisplay | 97 | 56 | | DateFieldDisplay | 240 | 52 | | CurrencyFieldDisplay | 236 | 110 | | FullNameFieldDisplay | 131 | 85 | | AddressFieldDisplay | 118 | 81 | | BooleanFieldDisplay | 130 | 100 | | JSONFieldDisplay | 248 | 49 | | LinksFieldDisplay | 1180 | 140 | | LinkFieldDisplay | 140 | 78 | | MultiSelectFieldDisplay | 770 | 130 | | SelectFieldDisplay | 230 | 87 |
This commit is contained in:
@ -3,10 +3,12 @@ import {
|
||||
mockedObjectMetadataItems,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
describe('getRecordNodeFromRecord', () => {
|
||||
it('computes relation records cache references by default', () => {
|
||||
// Given
|
||||
@ -19,7 +21,7 @@ describe('getRecordNodeFromRecord', () => {
|
||||
name: true,
|
||||
company: true,
|
||||
};
|
||||
const record = mockedPeopleData[0];
|
||||
const record = peopleMock[0];
|
||||
|
||||
// When
|
||||
const result = getRecordNodeFromRecord({
|
||||
@ -33,12 +35,12 @@ describe('getRecordNodeFromRecord', () => {
|
||||
expect(result).toEqual({
|
||||
__typename: 'Person',
|
||||
company: {
|
||||
__ref: 'Company:5c21e19e-e049-4393-8c09-3e3f8fb09ecb',
|
||||
__ref: `Company:${record.company.id}`,
|
||||
},
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Alexandre',
|
||||
lastName: 'Prot',
|
||||
firstName: record.name.firstName,
|
||||
lastName: record.name.lastName,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -54,7 +56,7 @@ describe('getRecordNodeFromRecord', () => {
|
||||
name: true,
|
||||
company: true,
|
||||
};
|
||||
const record = mockedPeopleData[0];
|
||||
const record = peopleMock[0];
|
||||
const computeReferences = false;
|
||||
|
||||
// When
|
||||
@ -72,8 +74,8 @@ describe('getRecordNodeFromRecord', () => {
|
||||
company: record.company,
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Alexandre',
|
||||
lastName: 'Prot',
|
||||
firstName: record.name.firstName,
|
||||
lastName: record.name.lastName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
export const query = gql`
|
||||
query FindDuplicatePerson($id: ID!) {
|
||||
@ -49,11 +51,11 @@ export const responseData = {
|
||||
personDuplicates: {
|
||||
edges: [
|
||||
{
|
||||
node: { ...mockedPeopleData[0], updatedAt: '' },
|
||||
node: { ...peopleMock[0], updatedAt: '' },
|
||||
cursor: 'cursor1',
|
||||
},
|
||||
{
|
||||
node: { ...mockedPeopleData[1], updatedAt: '' },
|
||||
node: { ...peopleMock[1], updatedAt: '' },
|
||||
cursor: 'cursor2',
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { useAddressField } from '@/object-record/record-field/meta-types/hooks/useAddressField';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useAddressFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useAddressFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
export const AddressFieldDisplay = () => {
|
||||
const { fieldValue } = useAddressField();
|
||||
const { fieldValue } = useAddressFieldDisplay();
|
||||
|
||||
const content = [
|
||||
fieldValue?.addressStreet1,
|
||||
@ -10,7 +12,7 @@ export const AddressFieldDisplay = () => {
|
||||
fieldValue?.addressCity,
|
||||
fieldValue?.addressCountry,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.filter(isNonEmptyString)
|
||||
.join(', ');
|
||||
|
||||
return <TextDisplay text={content} />;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useBooleanField } from '@/object-record/record-field/meta-types/hooks/useBooleanField';
|
||||
import { useBooleanFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useBooleanFieldDisplay';
|
||||
import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
|
||||
|
||||
export const BooleanFieldDisplay = () => {
|
||||
const { fieldValue } = useBooleanField();
|
||||
const { fieldValue } = useBooleanFieldDisplay();
|
||||
|
||||
return <BooleanDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useCurrencyFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useCurrencyFieldDisplay';
|
||||
import { CurrencyDisplay } from '@/ui/field/display/components/CurrencyDisplay';
|
||||
|
||||
import { useCurrencyField } from '../../hooks/useCurrencyField';
|
||||
|
||||
export const CurrencyFieldDisplay = () => {
|
||||
const { fieldValue } = useCurrencyField();
|
||||
const { fieldValue } = useCurrencyFieldDisplay();
|
||||
|
||||
return <CurrencyDisplay currencyValue={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useDateFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useDateFieldDisplay';
|
||||
import { DateDisplay } from '@/ui/field/display/components/DateDisplay';
|
||||
|
||||
import { useDateField } from '../../hooks/useDateField';
|
||||
|
||||
export const DateFieldDisplay = () => {
|
||||
const { fieldValue } = useDateField();
|
||||
const { fieldValue } = useDateFieldDisplay();
|
||||
|
||||
return <DateDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useDateTimeFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay';
|
||||
import { DateTimeDisplay } from '@/ui/field/display/components/DateTimeDisplay';
|
||||
|
||||
import { useDateTimeField } from '../../hooks/useDateTimeField';
|
||||
|
||||
export const DateTimeFieldDisplay = () => {
|
||||
const { fieldValue } = useDateTimeField();
|
||||
const { fieldValue } = useDateTimeFieldDisplay();
|
||||
|
||||
return <DateTimeDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useEmailFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useEmailFieldDisplay';
|
||||
import { EmailDisplay } from '@/ui/field/display/components/EmailDisplay';
|
||||
|
||||
import { useEmailField } from '../../hooks/useEmailField';
|
||||
|
||||
export const EmailFieldDisplay = () => {
|
||||
const { fieldValue } = useEmailField();
|
||||
const { fieldValue } = useEmailFieldDisplay();
|
||||
|
||||
return <EmailDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { useFullNameField } from '@/object-record/record-field/meta-types/hooks/useFullNameField';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useFullNameFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useFullNameFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
export const FullNameFieldDisplay = () => {
|
||||
const { fieldValue } = useFullNameField();
|
||||
const { fieldValue } = useFullNameFieldDisplay();
|
||||
|
||||
const content = [fieldValue.firstName, fieldValue.lastName]
|
||||
.filter(Boolean)
|
||||
const content = [fieldValue?.firstName, fieldValue?.lastName]
|
||||
.filter(isNonEmptyString)
|
||||
.join(' ');
|
||||
|
||||
return <TextDisplay text={content} />;
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField';
|
||||
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||
import { useJsonFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useJsonFieldDisplay';
|
||||
import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const JsonFieldDisplay = () => {
|
||||
const { fieldValue, maxWidth } = useJsonField();
|
||||
const { fieldValue, maxWidth } = useJsonFieldDisplay();
|
||||
|
||||
return (
|
||||
<JsonDisplay
|
||||
text={
|
||||
isFieldRawJsonValue(fieldValue) && isDefined(fieldValue)
|
||||
? JSON.stringify(fieldValue)
|
||||
: ''
|
||||
}
|
||||
maxWidth={maxWidth}
|
||||
/>
|
||||
);
|
||||
if (!isDefined(fieldValue)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const value = JSON.stringify(fieldValue);
|
||||
|
||||
return <JsonDisplay text={value} maxWidth={maxWidth} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useLinkFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useLinkFieldDisplay';
|
||||
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
|
||||
|
||||
import { useLinkField } from '../../hooks/useLinkField';
|
||||
|
||||
export const LinkFieldDisplay = () => {
|
||||
const { fieldValue } = useLinkField();
|
||||
const { fieldValue } = useLinkFieldDisplay();
|
||||
|
||||
return <LinkDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||
import { useLinksField } from '@/object-record/record-field/meta-types/hooks/useLinksField';
|
||||
import { useLinksFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useLinksFieldDisplay';
|
||||
import { LinksDisplay } from '@/ui/field/display/components/LinksDisplay';
|
||||
|
||||
export const LinksFieldDisplay = () => {
|
||||
const { fieldValue } = useLinksField();
|
||||
const { fieldValue } = useLinksFieldDisplay();
|
||||
|
||||
const { isFocused } = useFieldFocus();
|
||||
|
||||
|
||||
@ -1,23 +1,39 @@
|
||||
import { Tag } from 'twenty-ui';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Tag, THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
|
||||
import { useMultiSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useMultiSelectFieldDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
|
||||
const spacing1 = THEME_COMMON.spacing(1);
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${spacing1};
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const MultiSelectFieldDisplay = () => {
|
||||
const { fieldValues, fieldDefinition } = useMultiSelectField();
|
||||
const { fieldValue, fieldDefinition } = useMultiSelectFieldDisplay();
|
||||
|
||||
const { isFocused } = useFieldFocus();
|
||||
|
||||
const selectedOptions = fieldValues
|
||||
const selectedOptions = fieldValue
|
||||
? fieldDefinition.metadata.options?.filter((option) =>
|
||||
fieldValues.includes(option.value),
|
||||
fieldValue.includes(option.value),
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!selectedOptions) return null;
|
||||
|
||||
return (
|
||||
return isFocused ? (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
{selectedOptions.map((selectedOption, index) => (
|
||||
<Tag
|
||||
@ -27,5 +43,16 @@ export const MultiSelectFieldDisplay = () => {
|
||||
/>
|
||||
))}
|
||||
</ExpandableList>
|
||||
) : (
|
||||
<StyledContainer>
|
||||
{selectedOptions.map((selectedOption, index) => (
|
||||
<Tag
|
||||
preventShrink
|
||||
key={index}
|
||||
color={selectedOption.color}
|
||||
text={selectedOption.label}
|
||||
/>
|
||||
))}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useNumberFieldDisplay';
|
||||
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';
|
||||
|
||||
import { useNumberField } from '../../hooks/useNumberField';
|
||||
|
||||
export const NumberFieldDisplay = () => {
|
||||
const { fieldValue } = useNumberField();
|
||||
const { fieldValue } = useNumberFieldDisplay();
|
||||
|
||||
return <NumberDisplay value={fieldValue} />;
|
||||
};
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
import { Tag } from 'twenty-ui';
|
||||
|
||||
import { useSelectField } from '../../hooks/useSelectField';
|
||||
import { useSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useSelectFieldDisplay';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const SelectFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition } = useSelectField();
|
||||
const { fieldValue, fieldDefinition } = useSelectFieldDisplay();
|
||||
|
||||
const selectedOption = fieldDefinition.metadata.options?.find(
|
||||
(option) => option.value === fieldValue,
|
||||
);
|
||||
|
||||
return selectedOption ? (
|
||||
<Tag color={selectedOption.color} text={selectedOption.label} />
|
||||
) : (
|
||||
<></>
|
||||
if (!isDefined(selectedOption)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tag
|
||||
preventShrink
|
||||
color={selectedOption.color}
|
||||
text={selectedOption.label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useTextFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
import { useTextField } from '../../hooks/useTextField';
|
||||
|
||||
export const TextFieldDisplay = () => {
|
||||
const { fieldValue, maxWidth } = useTextField();
|
||||
const { fieldValue, maxWidth } = useTextFieldDisplay();
|
||||
|
||||
return <TextDisplay text={fieldValue} maxWidth={maxWidth} />;
|
||||
};
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useDateTimeField } from '../../../hooks/useDateTimeField';
|
||||
import { DateTimeFieldDisplay } from '../DateTimeFieldDisplay';
|
||||
|
||||
const formattedDate = new Date('2023-04-01');
|
||||
|
||||
const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useDateTimeField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'date',
|
||||
label: 'Date',
|
||||
type: FieldMetadataType.DateTime,
|
||||
iconName: 'IconCalendarEvent',
|
||||
metadata: {
|
||||
fieldName: 'Date',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<DateFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateTimeFieldDisplay,
|
||||
argTypes: { value: { control: 'date' } },
|
||||
args: {
|
||||
value: formattedDate,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateTimeFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
@ -1,67 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useEmailField } from '@/object-record/record-field/meta-types/hooks/useEmailField';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
|
||||
import { EmailFieldDisplay } from '../EmailFieldDisplay';
|
||||
|
||||
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useEmailField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'email',
|
||||
label: 'Email',
|
||||
type: FieldMetadataType.Email,
|
||||
iconName: 'IconLink',
|
||||
metadata: {
|
||||
fieldName: 'Email',
|
||||
placeHolder: 'Email',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<EmailFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: EmailFieldDisplay,
|
||||
args: {
|
||||
value: 'Test@Test.test',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EmailFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
@ -1,83 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useNumberField } from '../../../hooks/useNumberField';
|
||||
import { NumberFieldDisplay } from '../NumberFieldDisplay';
|
||||
|
||||
const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
|
||||
const { setFieldValue } = useNumberField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/NumberFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'number',
|
||||
label: 'Number',
|
||||
type: FieldMetadataType.Number,
|
||||
iconName: 'Icon123',
|
||||
metadata: {
|
||||
fieldName: 'Number',
|
||||
placeHolder: 'Number',
|
||||
isPositive: true,
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
useUpdateRecord: () => [() => undefined, {}],
|
||||
}}
|
||||
>
|
||||
<NumberFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: NumberFieldDisplay,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof NumberFieldDisplay>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
args: {
|
||||
value: 1e100,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Negative: Story = {
|
||||
args: {
|
||||
value: -1000,
|
||||
},
|
||||
};
|
||||
|
||||
export const Float: Story = {
|
||||
args: {
|
||||
value: 1.357802,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { AddressFieldDisplay } from '@/object-record/record-field/meta-types/display/components/AddressFieldDisplay';
|
||||
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/AddressFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testAddress'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: AddressFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof AddressFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator('person', 'testAddress', {
|
||||
addressCity:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam',
|
||||
addressCountry: 'United States',
|
||||
addressStreet1: '1234 Elm Street',
|
||||
addressStreet2: 'Apt 1234',
|
||||
addressLat: 0,
|
||||
addressLng: 0,
|
||||
addressPostcode: '12345',
|
||||
addressState: 'CA',
|
||||
} as FieldAddressValue),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'AddressFieldDisplay',
|
||||
averageThresholdInMs: 0.15,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/BooleanFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testBoolean'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: BooleanFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof BooleanFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'BooleanFieldDisplay',
|
||||
averageThresholdInMs: 0.15,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,64 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { CurrencyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/CurrencyFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/CurrencyFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('company', 'annualRecurringRevenue'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: CurrencyFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CurrencyFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Millions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 18200000 * 1000000,
|
||||
currencyCode: 'EUR',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Billions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 3230000000 * 1000000,
|
||||
currencyCode: 'USD',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Bazillions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1e100,
|
||||
currencyCode: 'USD',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'CurrencyFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { DateFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'createdAt'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'DateFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { DateTimeFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateTimeFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'createdAt'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateTimeFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateTimeFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'DateTimeFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { EmailFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'email'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: EmailFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EmailFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator(
|
||||
'person',
|
||||
'email',
|
||||
'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com',
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'EmailFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FullNameFieldDisplay } from '@/object-record/record-field/meta-types/display/components/FullNameFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/FullNameFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'name'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: FullNameFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof FullNameFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'FullNameFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { JsonFieldDisplay } from '@/object-record/record-field/meta-types/display/components/JsonFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/JsonFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testJson'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: JsonFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof JsonFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'JsonFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { LinkFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinkFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/LinkFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testLink'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: LinkFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof LinkFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'LinkFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,69 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldFocusContext } from '@/object-record/record-field/contexts/FieldFocusContext';
|
||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const FieldFocusEffect = () => {
|
||||
const { setIsFocused } = useContext(FieldFocusContext);
|
||||
|
||||
useEffect(() => {
|
||||
setIsFocused(true);
|
||||
}, [setIsFocused]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/LinksFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testLinks'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: LinksFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof LinksFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const ExpandableList: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<FieldFocusContextProvider>
|
||||
<FieldFocusEffect />
|
||||
<Story />
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'LinksFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,69 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldFocusContext } from '@/object-record/record-field/contexts/FieldFocusContext';
|
||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||
import { MultiSelectFieldDisplay } from '@/object-record/record-field/meta-types/display/components/MultiSelectFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const FieldFocusEffect = () => {
|
||||
const { setIsFocused } = useContext(FieldFocusContext);
|
||||
|
||||
useEffect(() => {
|
||||
setIsFocused(true);
|
||||
}, [setIsFocused]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/MultiSelectFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testMultiSelect'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: MultiSelectFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof MultiSelectFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const ExpandableList: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<FieldFocusContextProvider>
|
||||
<FieldFocusEffect />
|
||||
<Story />
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
container: { width: 130 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'MultiSelectFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,53 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { NumberFieldDisplay } from '@/object-record/record-field/meta-types/display/components/NumberFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/NumberFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('company', 'employees'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: NumberFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof NumberFieldDisplay>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', 1e100)],
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Negative: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', -1000)],
|
||||
};
|
||||
|
||||
export const Float: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', 3.14159)],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'NumberFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -33,9 +33,6 @@ export const Elipsis: Story = {
|
||||
};
|
||||
|
||||
export const WrongNumber: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
decorators: [getFieldDecorator('person', 'phone', 'sdklaskdj')],
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { SelectFieldDisplay } from '@/object-record/record-field/meta-types/display/components/SelectFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/SelectFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testSelect'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: SelectFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SelectFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'SelectFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -1,54 +1,22 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useTextField } from '../../../hooks/useTextField';
|
||||
import { TextFieldDisplay } from '../TextFieldDisplay';
|
||||
|
||||
const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useTextField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
import { TextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/TextFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/TextFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'text',
|
||||
label: 'Text',
|
||||
type: FieldMetadataType.Text,
|
||||
iconName: 'IconLink',
|
||||
metadata: {
|
||||
fieldName: 'Text',
|
||||
placeHolder: 'Text',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<TextFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'city'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: TextFieldDisplay,
|
||||
args: {
|
||||
value: 'Lorem ipsum',
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
@ -59,11 +27,21 @@ type Story = StoryObj<typeof TextFieldDisplay>;
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
args: {
|
||||
value:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator(
|
||||
'person',
|
||||
'city',
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'TextFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldAddressValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useAddressFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldAddressValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useBooleanFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<boolean | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldCurrencyValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useCurrencyFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldCurrencyValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useDateFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, clearable } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
clearable,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useDateTimeFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, clearable } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
clearable,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useEmailFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldFullNameValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useFullNameFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldFullNameValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useJsonFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, maxWidth } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldJsonValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldLinkValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useLinkFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
const fieldValue = useRecordFieldValue<FieldLinkValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldLinksValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useLinksFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldLinksValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMultiSelectMetadata,
|
||||
FieldMultiSelectValue,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
export const useMultiSelectFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { fieldName } = fieldDefinition.metadata;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldMultiSelectValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition:
|
||||
fieldDefinition as FieldDefinition<FieldMultiSelectMetadata>,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||
import { isFieldNumber } from '../../types/guards/isFieldNumber';
|
||||
|
||||
export const useNumberFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(FieldMetadataType.Number, isFieldNumber, fieldDefinition);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
const fieldValue = useRecordFieldValue<number | null | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
||||
@ -9,7 +9,10 @@ export const usePhoneFieldDisplay = () => {
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue(entityId, fieldName);
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
|
||||
@ -3,6 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { PreComputedChipGeneratorsContext } from '@/object-metadata/context/PreComputedChipGeneratorsContext';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -32,7 +33,10 @@ export const useRelationFieldDisplay = () => {
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue(entityId, fieldName);
|
||||
const fieldValue = useRecordFieldValue<ObjectRecord | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
const maxWidthForField =
|
||||
isDefined(button) && isDefined(maxWidth)
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import {
|
||||
FieldSelectMetadata,
|
||||
FieldSelectValue,
|
||||
} from '../../types/FieldMetadata';
|
||||
|
||||
export const useSelectFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { fieldName } = fieldDefinition.metadata;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldSelectValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition: fieldDefinition as FieldDefinition<FieldSelectMetadata>,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useTextFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, maxWidth } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue =
|
||||
useRecordFieldValue<string | undefined>(entityId, fieldName) ?? '';
|
||||
|
||||
return {
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
||||
@ -1,20 +1,26 @@
|
||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { mockObjectMetadataItem } from '~/testing/mock-data/objectMetadataItems';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems';
|
||||
|
||||
import { isRecordMatchingFilter } from './isRecordMatchingFilter';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
describe('isRecordMatchingFilter', () => {
|
||||
describe('Empty Filters', () => {
|
||||
it('matches any record when no filter is provided', () => {
|
||||
const emptyFilter = {};
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter: emptyFilter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -26,12 +32,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
employees: {},
|
||||
};
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter: filterWithEmptyFields,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -40,12 +46,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty and filter', () => {
|
||||
const filter = { and: [] };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -54,12 +60,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty or filter', () => {
|
||||
const filter = { or: [] };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -68,12 +74,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty not filter', () => {
|
||||
const filter = { not: {} };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -82,92 +88,161 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
describe('Simple Filters', () => {
|
||||
it('matches a record with a simple equality filter on name', () => {
|
||||
const filter = { name: { eq: 'Airbnb' } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter = { name: { eq: companyMockInFilter.name } };
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a simple equality filter on domainName', () => {
|
||||
const filter = { domainName: { eq: 'airbnb.com' } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
domainName: companyMockInFilter.domainName + 'Different',
|
||||
};
|
||||
|
||||
const filter = { domainName: { eq: companyMockInFilter.domainName } };
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a greater than filter on employees', () => {
|
||||
const filter = { employees: { gt: 10 } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
employees: companyMockInFilter.employees - 50,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
employees: { gt: companyMockInFilter.employees - 1 },
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a boolean filter on idealCustomerProfile', () => {
|
||||
const filter = { idealCustomerProfile: { eq: true } };
|
||||
const companyIdealCustomerProfileTrue = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
};
|
||||
|
||||
const companyIdealCustomerProfileFalse = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
idealCustomerProfile: {
|
||||
eq: companyIdealCustomerProfileTrue.idealCustomerProfile,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyIdealCustomerProfileTrue,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
).toBe(companyIdealCustomerProfileTrue.idealCustomerProfile);
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[4], // Assuming this record has idealCustomerProfile as false
|
||||
record: companyIdealCustomerProfileFalse,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
).toBe(companyIdealCustomerProfileFalse.idealCustomerProfile);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complex And/Or/Not Nesting', () => {
|
||||
it('matches record with a combination of and + or filters', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
employees: 0,
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ domainName: { eq: 'airbnb.com' } },
|
||||
{
|
||||
domainName: {
|
||||
eq: companyMockInFilter.domainName,
|
||||
},
|
||||
},
|
||||
{
|
||||
or: [
|
||||
{ employees: { gt: 10 } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{
|
||||
employees: {
|
||||
gt: companyMockInFilter.employees - 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -175,118 +250,181 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with nested not filter', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
not: {
|
||||
and: [
|
||||
{ name: { eq: 'Airbnb' } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false); // Should not match as it's Airbnb with idealCustomerProfile true
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[3], // Apple
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true); // Should match as it's not Airbnb
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('matches record with deep nesting of and, or, and not filters', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
domainName: companyMockInFilter.domainName + 'Different',
|
||||
employees: 5,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ domainName: { eq: 'apple.com' } },
|
||||
{ domainName: { eq: companyMockInFilter.domainName } },
|
||||
{
|
||||
or: [{ employees: { eq: 10 } }, { not: { name: { eq: 'Apple' } } }],
|
||||
or: [
|
||||
{ employees: { eq: companyMockInFilter.employees } },
|
||||
{ not: { name: { eq: companyMockInFilter.name } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[3], // Apple
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[4], // Qonto
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with and filter at root level', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ name: { eq: 'Facebook' } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[5], // Facebook
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with or filter at root level including a not condition', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
employees: companyMockInFilter.employees - 1,
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
or: [{ name: { eq: 'Sequoia' } }, { not: { employees: { eq: 1 } } }],
|
||||
or: [
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{ not: { employees: { eq: companyMockInFilter.employees - 1 } } },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[6], // Sequoia
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
@ -294,49 +432,75 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
describe('Implicit And Conditions', () => {
|
||||
it('matches record with implicit and of multiple operators within the same field', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
employees: companyMockInFilter.employees + 100,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
employees: { gt: 10, lt: 100000 },
|
||||
name: { eq: 'Airbnb' },
|
||||
employees: {
|
||||
gt: companyMockInFilter.employees - 10,
|
||||
lt: companyMockInFilter.employees + 10,
|
||||
},
|
||||
name: { eq: companyMockInFilter.name },
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true); // Matches as Airbnb's employee count is between 10 and 100000
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false); // Does not match as Aircall's employee count is not within the range
|
||||
});
|
||||
|
||||
it('matches record with implicit and within an object passed to or', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
domainName: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter = {
|
||||
or: {
|
||||
name: { eq: 'Airbnb' },
|
||||
domainName: { eq: 'airbnb.com' },
|
||||
name: { eq: companyMockInFilter.name },
|
||||
domainName: { eq: companyMockInFilter.domainName },
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[2], // Algolia
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@ -6,10 +6,12 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
|
||||
import { RecordDetailDuplicatesSection } from '../RecordDetailDuplicatesSection';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const meta: Meta<typeof RecordDetailDuplicatesSection> = {
|
||||
title:
|
||||
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailDuplicatesSection',
|
||||
@ -21,7 +23,7 @@ const meta: Meta<typeof RecordDetailDuplicatesSection> = {
|
||||
MemoryRouterDecorator,
|
||||
],
|
||||
args: {
|
||||
objectRecordId: mockedCompaniesData[0].id,
|
||||
objectRecordId: companiesMock[0].id,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
},
|
||||
parameters: {
|
||||
|
||||
@ -8,12 +8,16 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
|
||||
import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
import { RecordDetailRelationSection } from '../RecordDetailRelationSection';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
title:
|
||||
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection',
|
||||
@ -22,7 +26,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
(Story) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: mockedCompaniesData[0].id,
|
||||
entityId: companiesMock[0].id,
|
||||
basePathToShowPage: '/object-record/',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
|
||||
@ -44,7 +48,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
records: mockedCompaniesData,
|
||||
records: companiesMock,
|
||||
},
|
||||
};
|
||||
|
||||
@ -58,10 +62,10 @@ export const WithRecords: Story = {
|
||||
parameters: {
|
||||
records: [
|
||||
{
|
||||
...mockedCompaniesData[0],
|
||||
people: mockedPeopleData,
|
||||
...companiesMock[0],
|
||||
people: peopleMock,
|
||||
},
|
||||
...mockedPeopleData,
|
||||
...peopleMock,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -36,13 +36,16 @@ export const useRecordValue = (recordId: string) => {
|
||||
return tableValue?.[recordId] as ObjectRecord | undefined;
|
||||
};
|
||||
|
||||
export const useRecordFieldValue = (recordId: string, fieldName: string) => {
|
||||
export const useRecordFieldValue = <T,>(
|
||||
recordId: string,
|
||||
fieldName: string,
|
||||
) => {
|
||||
const recordFieldValues = useContextSelector(
|
||||
RecordFieldValueSelectorContext,
|
||||
(value) => value[0],
|
||||
);
|
||||
|
||||
return recordFieldValues?.[recordId]?.[fieldName];
|
||||
return recordFieldValues?.[recordId]?.[fieldName] as T;
|
||||
};
|
||||
|
||||
export const useSetRecordFieldValue = () => {
|
||||
|
||||
@ -8,16 +8,18 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
|
||||
import { RelationPickerDecorator } from '~/testing/decorators/RelationPickerDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { EntityForSelect } from '../../types/EntityForSelect';
|
||||
import { SingleEntitySelect } from '../SingleEntitySelect';
|
||||
|
||||
const entities = mockedPeopleData.map<EntityForSelect>((person) => ({
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const entities = peopleMock.map<EntityForSelect>((person) => ({
|
||||
id: person.id,
|
||||
name: person.name.firstName + ' ' + person.name.lastName,
|
||||
avatarUrl: person.avatarUrl,
|
||||
avatarUrl: 'https://picsum.photos/200',
|
||||
avatarType: 'rounded',
|
||||
record: { ...person, __typename: 'Person' },
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user