From a946c6a33de378c1cb6818efee8f0d891208ded7 Mon Sep 17 00:00:00 2001 From: sid0-0 <43578323+sid0-0@users.noreply.github.com> Date: Thu, 3 Oct 2024 22:25:29 +0530 Subject: [PATCH 01/16] fix: validate emails in record-fields (#7245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: #7149 Introduced a minimal field validation framework for record-fields. Currently only shows errors for email field. image image --------- Co-authored-by: sid0-0 Co-authored-by: bosiraphael Co-authored-by: Félix Malfait --- .../input/components/EmailsFieldInput.tsx | 12 +++- .../input/components/LinksFieldInput.tsx | 5 +- .../input/components/MultiItemFieldInput.tsx | 26 ++++++- .../validation-schemas/emailSchema.ts | 3 + .../dropdown/components/DropdownMenuInput.tsx | 72 ++++++++++++------- 5 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/validation-schemas/emailSchema.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx index 32418cebc607..d933aeabcd06 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx @@ -1,6 +1,7 @@ import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField'; import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem'; -import { useMemo } from 'react'; +import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema'; +import { useCallback, useMemo } from 'react'; import { isDefined } from 'twenty-ui'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { MultiItemFieldInput } from './MultiItemFieldInput'; @@ -29,6 +30,14 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { }); }; + const validateInput = useCallback( + (input: string) => ({ + isValid: emailSchema.safeParse(input).success, + errorMessage: '', + }), + [], + ); + const isPrimaryEmail = (index: number) => index === 0 && emails?.length > 1; return ( @@ -38,6 +47,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { onCancel={onCancel} placeholder="Email" fieldMetadataType={FieldMetadataType.Emails} + validateInput={validateInput} renderItem={({ value: email, index, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx index 97bc9578c97c..e52cc95c041f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx @@ -51,7 +51,10 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => { onCancel={onCancel} placeholder="URL" fieldMetadataType={FieldMetadataType.Links} - validateInput={(input) => absoluteUrlSchema.safeParse(input).success} + validateInput={(input) => ({ + isValid: absoluteUrlSchema.safeParse(input).success, + errorMessage: '', + })} formatInput={(input) => ({ url: input, label: '' })} renderItem={({ value: link, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx index a73e49498805..7e3e93ec2c48 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx @@ -30,7 +30,7 @@ type MultiItemFieldInputProps = { onPersist: (updatedItems: T[]) => void; onCancel?: () => void; placeholder: string; - validateInput?: (input: string) => boolean; + validateInput?: (input: string) => { isValid: boolean; errorMessage: string }; formatInput?: (input: string) => T; renderItem: (props: { value: T; @@ -74,8 +74,21 @@ export const MultiItemFieldInput = ({ const [isInputDisplayed, setIsInputDisplayed] = useState(false); const [inputValue, setInputValue] = useState(''); const [itemToEditIndex, setItemToEditIndex] = useState(-1); + const [errorData, setErrorData] = useState({ + isValid: true, + errorMessage: '', + }); const isAddingNewItem = itemToEditIndex === -1; + const handleOnChange = (value: string) => { + setInputValue(value); + if (!validateInput) return; + + if (errorData.isValid) { + setErrorData(errorData); + } + }; + const handleAddButtonClick = () => { setItemToEditIndex(-1); setIsInputDisplayed(true); @@ -105,7 +118,13 @@ export const MultiItemFieldInput = ({ }; const handleSubmitInput = () => { - if (validateInput !== undefined && !validateInput(inputValue)) return; + if (validateInput !== undefined) { + const validationData = validateInput(inputValue) ?? { isValid: true }; + if (!validationData.isValid) { + setErrorData(validationData); + return; + } + } const newItem = formatInput ? formatInput(inputValue) @@ -160,6 +179,7 @@ export const MultiItemFieldInput = ({ placeholder={placeholder} value={inputValue} hotkeyScope={hotkeyScope} + hasError={!errorData.isValid} renderInput={ renderInput ? (props) => @@ -170,7 +190,7 @@ export const MultiItemFieldInput = ({ }) : undefined } - onChange={(event) => setInputValue(event.target.value)} + onChange={(event) => handleOnChange(event.target.value)} onEnter={handleSubmitInput} rightComponent={ ` +const StyledInput = styled.input<{ + withRightComponent?: boolean; + hasError?: boolean; +}>` ${TEXT_INPUT_STYLE} - border: 1px solid ${({ theme }) => theme.border.color.medium}; + border: 1px solid ${({ theme, hasError }) => + hasError ? theme.border.color.danger : theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.sm}; box-sizing: border-box; font-weight: ${({ theme }) => theme.font.weight.medium}; @@ -19,8 +23,10 @@ const StyledInput = styled.input<{ withRightComponent?: boolean }>` width: 100%; &:focus { - border-color: ${({ theme }) => theme.color.blue}; - box-shadow: 0px 0px 0px 3px ${({ theme }) => RGBA(theme.color.blue, 0.1)}; + ${({ theme, hasError = false }) => { + if (hasError) return ''; + return `box-shadow: 0px 0px 0px 3px ${RGBA(theme.color.blue, 0.1)}`; + }}; } ${({ withRightComponent }) => @@ -44,6 +50,12 @@ const StyledRightContainer = styled.div` transform: translateY(-50%); `; +const StyledErrorDiv = styled.div` + color: ${({ theme }) => theme.color.red}; + padding: 0 ${({ theme }) => theme.spacing(2)} + ${({ theme }) => theme.spacing(1)}; +`; + type HTMLInputProps = InputHTMLAttributes; export type DropdownMenuInputProps = HTMLInputProps & { @@ -60,6 +72,8 @@ export type DropdownMenuInputProps = HTMLInputProps & { autoFocus: HTMLInputProps['autoFocus']; placeholder: HTMLInputProps['placeholder']; }) => React.ReactNode; + error?: string | null; + hasError?: boolean; }; export const DropdownMenuInput = forwardRef< @@ -81,6 +95,8 @@ export const DropdownMenuInput = forwardRef< onTab, rightComponent, renderInput, + error = '', + hasError = false, }, ref, ) => { @@ -99,28 +115,32 @@ export const DropdownMenuInput = forwardRef< }); return ( - - {renderInput ? ( - renderInput({ - value, - onChange, - autoFocus, - placeholder, - }) - ) : ( - - )} - {!!rightComponent && ( - {rightComponent} - )} - + <> + + {renderInput ? ( + renderInput({ + value, + onChange, + autoFocus, + placeholder, + }) + ) : ( + + )} + {!!rightComponent && ( + {rightComponent} + )} + + {error && {error}} + ); }, ); From e3ed57442065cdad41f8ec58e4d4abc9349b4826 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:01:03 +0530 Subject: [PATCH 02/16] minor fix - reset single entity search (#7420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit minor follow up fix #7285 --------- Co-authored-by: Félix Malfait --- packages/twenty-front/src/generated-metadata/graphql.ts | 2 ++ .../components/RecordBoardColumnHeader.tsx | 1 - .../record-board-column/hooks/useAddNewCard.ts | 7 ++++++- .../settings/data-model/constants/RelationTypes.ts | 8 ++++++++ .../components/SettingsDataModelFieldRelationForm.tsx | 6 +++++- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 4f48fe60c424..64d5248023f6 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -992,6 +992,7 @@ export type RelationDefinition = { /** Relation definition type */ export enum RelationDefinitionType { + ManyToMany = 'MANY_TO_MANY', ManyToOne = 'MANY_TO_ONE', OneToMany = 'ONE_TO_MANY', OneToOne = 'ONE_TO_ONE' @@ -999,6 +1000,7 @@ export enum RelationDefinitionType { /** Type of the relation */ export enum RelationMetadataType { + ManyToMany = 'MANY_TO_MANY', ManyToOne = 'MANY_TO_ONE', OneToMany = 'ONE_TO_MANY', OneToOne = 'ONE_TO_ONE' diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx index d0d7e93de02c..e0ae827280d9 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx @@ -93,7 +93,6 @@ export const RecordBoardColumnHeader = () => { newRecord, handleNewButtonClick, handleCreateSuccess, - handleEntitySelect, } = useColumnNewCardActions(columnDefinition.id); const { isOpportunitiesCompanyFieldDisabled } = diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts index f0921f82f380..97cb8c5d6052 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts @@ -1,6 +1,7 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; +import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; @@ -17,6 +18,9 @@ export const useAddNewCard = () => { const columnContext = useContext(RecordBoardColumnContext); const { createOneRecord, selectFieldMetadataItem } = useContext(RecordBoardContext); + const { resetSearchFilter } = useEntitySelectSearch({ + relationPickerScopeId: 'relation-picker', + }); const { goBackToPreviousHotkeyScope, @@ -132,11 +136,12 @@ export const useAddNewCard = () => { company: null, }, ); + resetSearchFilter(); if (isOpportunity === true) { goBackToPreviousHotkeyScope(); } }, - [getColumnDefinitionId, goBackToPreviousHotkeyScope], + [getColumnDefinitionId, goBackToPreviousHotkeyScope, resetSearchFilter], ); const handleCreate = ( diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts b/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts index da5e9b8b9617..179f60fe8979 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts @@ -1,5 +1,6 @@ import { IconComponent, + IllustrationIconManyToMany, IllustrationIconOneToMany, IllustrationIconOneToOne, } from 'twenty-ui'; @@ -34,4 +35,11 @@ export const RELATION_TYPES: Record< imageSrc: OneToManySvg, isImageFlipped: true, }, + // Not supported yet + [RelationDefinitionType.ManyToMany]: { + label: 'Belongs to many', + Icon: IllustrationIconManyToMany, + imageSrc: OneToManySvg, + isImageFlipped: true, + }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx index 63305b53d04d..83f81326436a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx @@ -66,7 +66,11 @@ const StyledInputsContainer = styled.div` `; const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES) - .filter(([value]) => 'ONE_TO_ONE' !== value && 'MANY_TO_MANY' !== value) + .filter( + ([value]) => + RelationDefinitionType.OneToOne !== value && + RelationDefinitionType.ManyToMany !== value, + ) .map(([value, { label, Icon }]) => ({ label, value: value as RelationType, From 97eff774bdfd7590ed077d77b8dbfcd862d916f4 Mon Sep 17 00:00:00 2001 From: "gitstart-app[bot]" <57568882+gitstart-app[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:45:25 +0200 Subject: [PATCH 03/16] Allow input and display of floats for Number fields (#7340) ### Description - We added a decimal field for a Number Field type in the settings - We updated the Number Field type create a form with decimals input - We are not implementing the dropdown present on the Figma because it seems not related ### Demo Fixes #6987 --------- Co-authored-by: gitstart-twenty Co-authored-by: Marie Stoppa --- .../hooks/__mocks__/useFieldMetadataItem.ts | 1 + .../types/FieldMetadataItem.ts | 2 +- ...ormatFieldMetadataItemAsFieldDefinition.ts | 1 + .../components/RecordBoardCard.tsx | 1 + .../display/components/NumberFieldDisplay.tsx | 10 +- .../meta-types/hooks/useNumberField.ts | 11 +- .../record-field/types/FieldDefinition.ts | 3 + .../numberFieldDefaultValueSchema.ts | 5 + ...SettingsDataModelFieldSettingsFormCard.tsx | 23 ++- ...ttingsDataModelFieldNumberDecimalInput.tsx | 168 ++++++++++++++++++ .../SettingsDataModelFieldNumberForm.tsx | 55 ++++++ ...gsDataModelFieldNumberSettingsFormCard.tsx | 45 +++++ .../display/components/NumberDisplay.tsx | 7 +- .../utils/mapViewFieldsToColumnDefinitions.ts | 1 + .../__tests__/cast-as-integer-or-null.test.ts | 72 -------- .../__tests__/cast-as-number-or-null.test.ts | 72 ++++++++ ...r-or-null.ts => cast-as-number-or-null.ts} | 12 +- .../twenty-front/src/utils/format/number.ts | 10 +- .../field-metadata-validation.service.ts | 48 +++++ .../field-metadata/field-metadata.module.ts | 7 +- .../field-metadata/field-metadata.service.ts | 17 +- .../field-metadata-settings.interface.ts | 1 + 22 files changed, 478 insertions(+), 94 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx delete mode 100644 packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts create mode 100644 packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts rename packages/twenty-front/src/utils/{cast-as-integer-or-null.ts => cast-as-number-or-null.ts} (82%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts index f3c3e93f1b2c..de52cbd8fe73 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts @@ -51,6 +51,7 @@ export const queries = { ${baseFields} defaultValue options + settings } } `, diff --git a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts index 61ce60263dfc..8a403a55a379 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts @@ -17,7 +17,7 @@ export type FieldMetadataItemOption = { export type FieldMetadataItem = Omit< Field, - '__typename' | 'defaultValue' | 'options' | 'settings' | 'relationDefinition' + '__typename' | 'defaultValue' | 'options' | 'relationDefinition' > & { __typename?: string; defaultValue?: any; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts index 8ba9ebe23315..f372cd2eb3ac 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts @@ -54,5 +54,6 @@ export const formatFieldMetadataItemAsFieldDefinition = ({ metadata: fieldDefintionMetadata, type: field.type, }), + settings: field.settings, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index f7dde7fa911d..ef4aef726458 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -338,6 +338,7 @@ export const RecordBoardCard = ({ metadata: fieldDefinition.metadata, type: fieldDefinition.type, }), + settings: fieldDefinition.settings, }, useUpdateRecord: useUpdateOneRecordHook, hotkeyScope: InlineCellHotkeyScope.InlineCell, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx index 087a4117c47b..cb30dbed3776 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx @@ -2,7 +2,11 @@ import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/h import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay'; export const NumberFieldDisplay = () => { - const { fieldValue } = useNumberFieldDisplay(); - - return ; + const { fieldValue, fieldDefinition } = useNumberFieldDisplay(); + return ( + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts index 5bdceda11e73..097bcb8beef5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts @@ -5,10 +5,11 @@ import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecor import { FieldNumberValue } from '@/object-record/record-field/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { FieldMetadataType } from '~/generated-metadata/graphql'; + import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '~/utils/cast-as-integer-or-null'; + canBeCastAsNumberOrNull, + castAsNumberOrNull, +} from '~/utils/cast-as-number-or-null'; import { FieldContext } from '../../contexts/FieldContext'; import { usePersistField } from '../../hooks/usePersistField'; @@ -32,11 +33,11 @@ export const useNumberField = () => { const persistField = usePersistField(); const persistNumberField = (newValue: string) => { - if (!canBeCastAsIntegerOrNull(newValue)) { + if (!canBeCastAsNumberOrNull(newValue)) { return; } - const castedValue = castAsIntegerOrNull(newValue); + const castedValue = castAsNumberOrNull(newValue); persistField(castedValue); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts index 16f05f2418d2..1d8107c17953 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts @@ -16,4 +16,7 @@ export type FieldDefinition = { infoTooltipContent?: string; defaultValue?: any; editButtonIcon?: IconComponent; + settings?: { + decimals?: number; + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts new file mode 100644 index 000000000000..48981e4a5a34 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const numberFieldDefaultValueSchema = z.object({ + decimals: z.number().nullable(), +}); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index a71cc1654bcf..882b57f7d1c1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -11,6 +11,8 @@ import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/ import { SettingsDataModelFieldCurrencySettingsFormCard } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencySettingsFormCard'; import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm'; import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard'; +import { settingsDataModelFieldNumberFormSchema } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm'; +import { SettingsDataModelFieldNumberSettingsFormCard } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard'; import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm'; import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard'; import { @@ -52,6 +54,10 @@ const multiSelectFieldFormSchema = z .object({ type: z.literal(FieldMetadataType.MultiSelect) }) .merge(settingsDataModelFieldMultiSelectFormSchema); +const numberFieldFormSchema = z + .object({ type: z.literal(FieldMetadataType.Number) }) + .merge(settingsDataModelFieldNumberFormSchema); + const otherFieldsFormSchema = z.object({ type: z.enum( Object.keys( @@ -63,6 +69,7 @@ const otherFieldsFormSchema = z.object({ FieldMetadataType.MultiSelect, FieldMetadataType.Date, FieldMetadataType.DateTime, + FieldMetadataType.Number, ]), ) as [FieldMetadataType, ...FieldMetadataType[]], ), @@ -78,13 +85,17 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion( relationFieldFormSchema, selectFieldFormSchema, multiSelectFieldFormSchema, + numberFieldFormSchema, otherFieldsFormSchema, ], ); type SettingsDataModelFieldSettingsFormCardProps = { isCreatingField?: boolean; - fieldMetadataItem: Pick & + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'isCustom' + > & Partial>; } & Pick; @@ -163,6 +174,16 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } + if (fieldMetadataItem.type === FieldMetadataType.Number) { + return ( + + ); + } + if ( fieldMetadataItem.type === FieldMetadataType.Select || fieldMetadataItem.type === FieldMetadataType.MultiSelect diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx new file mode 100644 index 000000000000..706bcea37154 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx @@ -0,0 +1,168 @@ +import styled from '@emotion/styled'; + +import { Button } from '@/ui/input/button/components/Button'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { IconInfoCircle, IconMinus, IconPlus } from 'twenty-ui'; +import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null'; + +type SettingsDataModelFieldNumberDecimalsInputProps = { + value: number; + onChange: (value: number) => void; + disabled?: boolean; +}; + +const StyledCounterContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.noisy}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: 4px; + display: flex; + flex-direction: column; + flex: 1; + gap: ${({ theme }) => theme.spacing(1)}; + justify-content: center; +`; + +const StyledExampleText = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: ${({ theme }) => theme.font.weight.regular}; +`; + +const StyledCounterControlsIcons = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCounterInnerContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(2)}; + height: 24px; +`; + +const StyledTextInput = styled(TextInput)` + width: ${({ theme }) => theme.spacing(16)}; + input { + width: ${({ theme }) => theme.spacing(16)}; + height: ${({ theme }) => theme.spacing(6)}; + text-align: center; + font-weight: ${({ theme }) => theme.font.weight.medium}; + background: ${({ theme }) => theme.background.noisy}; + } + input ~ div { + padding-right: ${({ theme }) => theme.spacing(0)}; + border-radius: ${({ theme }) => theme.spacing(1)}; + background: ${({ theme }) => theme.background.noisy}; + } +`; + +const StyledTitle = styled.div` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledControlButton = styled(Button)` + height: ${({ theme }) => theme.spacing(6)}; + width: ${({ theme }) => theme.spacing(6)}; + padding: 0; + justify-content: center; + svg { + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(4)}; + } +`; + +const StyledInfoButton = styled(Button)` + height: ${({ theme }) => theme.spacing(6)}; + width: ${({ theme }) => theme.spacing(6)}; + padding: 0; + justify-content: center; + svg { + color: ${({ theme }) => theme.font.color.extraLight}; + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(4)}; + } +`; + +const MIN_VALUE = 0; +const MAX_VALUE = 100; +export const SettingsDataModelFieldNumberDecimalsInput = ({ + value, + onChange, + disabled, +}: SettingsDataModelFieldNumberDecimalsInputProps) => { + const exampleValue = (1000).toFixed(value); + + const handleIncrementCounter = () => { + if (value < MAX_VALUE) { + const newValue = value + 1; + onChange(newValue); + } + }; + + const handleDecrementCounter = () => { + if (value > MIN_VALUE) { + const newValue = value - 1; + onChange(newValue); + } + }; + + const handleTextInputChange = (value: string) => { + const castedNumber = castAsNumberOrNull(value); + if (castedNumber === null) { + onChange(MIN_VALUE); + return; + } + + if (castedNumber < MIN_VALUE) { + return; + } + + if (castedNumber > MAX_VALUE) { + onChange(MAX_VALUE); + return; + } + onChange(castedNumber); + }; + return ( + <> + Number of decimals + + + Example: {exampleValue} + + + + handleTextInputChange(value)} + disabled={disabled} + /> + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx new file mode 100644 index 000000000000..3a80bf0e6104 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx @@ -0,0 +1,55 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import { z } from 'zod'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema'; +import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput'; +import { CardContent } from '@/ui/layout/card/components/CardContent'; +import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number'; + +export const settingsDataModelFieldNumberFormSchema = z.object({ + settings: numberFieldDefaultValueSchema, +}); + +export type SettingsDataModelFieldNumberFormValues = z.infer< + typeof settingsDataModelFieldNumberFormSchema +>; + +type SettingsDataModelFieldNumberFormProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' | 'settings' + >; +}; + +export const SettingsDataModelFieldNumberForm = ({ + disabled, + fieldMetadataItem, +}: SettingsDataModelFieldNumberFormProps) => { + const { control } = useFormContext(); + + return ( + + { + const count = value?.decimals ?? 0; + + return ( + onChange({ decimals: value })} + disabled={disabled} + > + ); + }} + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx new file mode 100644 index 000000000000..edea86760fbf --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx @@ -0,0 +1,45 @@ +import styled from '@emotion/styled'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard'; +import { SettingsDataModelFieldNumberForm } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm'; +import { + SettingsDataModelFieldPreviewCard, + SettingsDataModelFieldPreviewCardProps, +} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard'; + +type SettingsDataModelFieldNumberSettingsFormCardProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' + >; +} & Pick; + +const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` + display: grid; + flex: 1 1 100%; +`; + +export const SettingsDataModelFieldNumberSettingsFormCard = ({ + disabled, + fieldMetadataItem, + objectMetadataItem, +}: SettingsDataModelFieldNumberSettingsFormCardProps) => { + return ( + + } + form={ + + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx index 1834e502a051..cef5ff6e0b81 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx @@ -4,8 +4,11 @@ import { EllipsisDisplay } from './EllipsisDisplay'; type NumberDisplayProps = { value: string | number | null | undefined; + decimals?: number; }; -export const NumberDisplay = ({ value }: NumberDisplayProps) => ( - {value && formatNumber(Number(value))} +export const NumberDisplay = ({ value, decimals }: NumberDisplayProps) => ( + + {value && formatNumber(Number(value), decimals)} + ); diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts index 139ac042751f..cbf5f19b35ae 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts @@ -50,6 +50,7 @@ export const mapViewFieldsToColumnDefinitions = ({ isSortable: correspondingColumnDefinition.isSortable, isFilterable: correspondingColumnDefinition.isFilterable, defaultValue: correspondingColumnDefinition.defaultValue, + settings: correspondingColumnDefinition.settings, } as ColumnDefinition; }) .filter(isDefined); diff --git a/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts b/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts deleted file mode 100644 index cc077afdb27c..000000000000 --- a/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '../cast-as-integer-or-null'; - -describe('canBeCastAsIntegerOrNull', () => { - it(`should return true if null`, () => { - expect(canBeCastAsIntegerOrNull(null)).toBeTruthy(); - }); - - it(`should return true if number`, () => { - expect(canBeCastAsIntegerOrNull(9)).toBeTruthy(); - }); - - it(`should return true if empty string`, () => { - expect(canBeCastAsIntegerOrNull('')).toBeTruthy(); - }); - - it(`should return true if integer string`, () => { - expect(canBeCastAsIntegerOrNull('9')).toBeTruthy(); - }); - - it(`should return false if undefined`, () => { - expect(canBeCastAsIntegerOrNull(undefined)).toBeFalsy(); - }); - - it(`should return false if non numeric string`, () => { - expect(canBeCastAsIntegerOrNull('9a')).toBeFalsy(); - }); - - it(`should return false if non numeric string #2`, () => { - expect(canBeCastAsIntegerOrNull('a9a')).toBeFalsy(); - }); - - it(`should return false if float`, () => { - expect(canBeCastAsIntegerOrNull(0.9)).toBeFalsy(); - }); - - it(`should return false if float string`, () => { - expect(canBeCastAsIntegerOrNull('0.9')).toBeFalsy(); - }); -}); - -describe('castAsIntegerOrNull', () => { - it(`should cast null to null`, () => { - expect(castAsIntegerOrNull(null)).toBe(null); - }); - - it(`should cast empty string to null`, () => { - expect(castAsIntegerOrNull('')).toBe(null); - }); - - it(`should cast an integer to an integer`, () => { - expect(castAsIntegerOrNull(9)).toBe(9); - }); - - it(`should cast an integer string to an integer`, () => { - expect(castAsIntegerOrNull('9')).toBe(9); - }); - - it(`should throw if trying to cast a float string to an integer`, () => { - expect(() => castAsIntegerOrNull('9.9')).toThrow(Error); - }); - - it(`should throw if trying to cast a non numeric string to an integer`, () => { - expect(() => castAsIntegerOrNull('9.9a')).toThrow(Error); - }); - - it(`should throw if trying to cast an undefined to an integer`, () => { - expect(() => castAsIntegerOrNull(undefined)).toThrow(Error); - }); -}); diff --git a/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts b/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts new file mode 100644 index 000000000000..082527de7ec7 --- /dev/null +++ b/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts @@ -0,0 +1,72 @@ +import { + canBeCastAsNumberOrNull, + castAsNumberOrNull, +} from '../cast-as-number-or-null'; + +describe('canBeCastAsNumberOrNull', () => { + it(`should return true if null`, () => { + expect(canBeCastAsNumberOrNull(null)).toBeTruthy(); + }); + + it(`should return true if number`, () => { + expect(canBeCastAsNumberOrNull(9)).toBeTruthy(); + }); + + it(`should return true if empty string`, () => { + expect(canBeCastAsNumberOrNull('')).toBeTruthy(); + }); + + it(`should return true if integer string`, () => { + expect(canBeCastAsNumberOrNull('9')).toBeTruthy(); + }); + + it(`should return false if undefined`, () => { + expect(canBeCastAsNumberOrNull(undefined)).toBeFalsy(); + }); + + it(`should return false if non numeric string`, () => { + expect(canBeCastAsNumberOrNull('9a')).toBeFalsy(); + }); + + it(`should return false if non numeric string #2`, () => { + expect(canBeCastAsNumberOrNull('a9a')).toBeFalsy(); + }); + + it(`should return true if float`, () => { + expect(canBeCastAsNumberOrNull(0.9)).toBeTruthy(); + }); + + it(`should return true if float string`, () => { + expect(canBeCastAsNumberOrNull('0.9')).toBeTruthy(); + }); +}); + +describe('castAsNumberOrNull', () => { + it(`should cast null to null`, () => { + expect(castAsNumberOrNull(null)).toBe(null); + }); + + it(`should cast empty string to null`, () => { + expect(castAsNumberOrNull('')).toBe(null); + }); + + it(`should cast an integer to an integer`, () => { + expect(castAsNumberOrNull(9)).toBe(9); + }); + + it(`should cast an integer string to an integer`, () => { + expect(castAsNumberOrNull('9')).toBe(9); + }); + + it(`should throw if trying to cast a float string to an integer`, () => { + expect(castAsNumberOrNull('9.9')).toBe(9.9); + }); + + it(`should throw if trying to cast a non numeric string to an integer`, () => { + expect(() => castAsNumberOrNull('9.9a')).toThrow(Error); + }); + + it(`should throw if trying to cast an undefined to an integer`, () => { + expect(() => castAsNumberOrNull(undefined)).toThrow(Error); + }); +}); diff --git a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts b/packages/twenty-front/src/utils/cast-as-number-or-null.ts similarity index 82% rename from packages/twenty-front/src/utils/cast-as-integer-or-null.ts rename to packages/twenty-front/src/utils/cast-as-number-or-null.ts index 5cca0021dead..ef06e5b5a33e 100644 --- a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts +++ b/packages/twenty-front/src/utils/cast-as-number-or-null.ts @@ -4,7 +4,7 @@ import { logError } from './logError'; const DEBUG_MODE = false; -export const canBeCastAsIntegerOrNull = ( +export const canBeCastAsNumberOrNull = ( probableNumberOrNull: string | undefined | number | null, ): probableNumberOrNull is number | null => { if (probableNumberOrNull === undefined) { @@ -16,7 +16,7 @@ export const canBeCastAsIntegerOrNull = ( if (isNumber(probableNumberOrNull)) { if (DEBUG_MODE) logError('typeof probableNumberOrNull === "number"'); - return Number.isInteger(probableNumberOrNull); + return true; } if (isNull(probableNumberOrNull)) { @@ -39,8 +39,8 @@ export const canBeCastAsIntegerOrNull = ( return false; } - if (Number.isInteger(stringAsNumber)) { - if (DEBUG_MODE) logError('Number.isInteger(stringAsNumber)'); + if (isNumber(stringAsNumber)) { + if (DEBUG_MODE) logError('isNumber(stringAsNumber)'); return true; } @@ -49,10 +49,10 @@ export const canBeCastAsIntegerOrNull = ( return false; }; -export const castAsIntegerOrNull = ( +export const castAsNumberOrNull = ( probableNumberOrNull: string | undefined | number | null, ): number | null => { - if (canBeCastAsIntegerOrNull(probableNumberOrNull) === false) { + if (canBeCastAsNumberOrNull(probableNumberOrNull) === false) { throw new Error('Cannot cast to number or null'); } diff --git a/packages/twenty-front/src/utils/format/number.ts b/packages/twenty-front/src/utils/format/number.ts index 4937372d0cbd..a36cb6fffad8 100644 --- a/packages/twenty-front/src/utils/format/number.ts +++ b/packages/twenty-front/src/utils/format/number.ts @@ -1,2 +1,8 @@ -export const formatNumber = (value: number): string => - value.toLocaleString('en-US'); +export const DEFAULT_DECIMAL_VALUE = 0; + +export const formatNumber = (value: number, decimals?: number): string => { + return value.toLocaleString('en-US', { + minimumFractionDigits: decimals ?? DEFAULT_DECIMAL_VALUE, + maximumFractionDigits: decimals ?? DEFAULT_DECIMAL_VALUE, + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts new file mode 100644 index 000000000000..81ede4ec4faa --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; + +import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; + +@Injectable() +export class FieldMetadataValidationService< + T extends FieldMetadataType | 'default' = 'default', +> { + constructor() {} + + validateSettingsOrThrow({ + fieldType, + settings, + }: { + fieldType: FieldMetadataType; + settings: FieldMetadataSettings; + }) { + switch (fieldType) { + case FieldMetadataType.NUMBER: + this.validateNumberSettings(settings); + break; + default: + break; + } + } + + private validateNumberSettings(settings: FieldMetadataSettings) { + if ('decimals' in settings) { + const { decimals } = settings; + + if ( + decimals !== undefined && + (decimals < 0 || !Number.isInteger(decimals)) + ) { + throw new FieldMetadataException( + `Decimals value "${decimals}" must be a positive integer`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + } + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index 377575ba9bfd..b6cb8e8ed1d7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -12,6 +12,7 @@ import { ActorModule } from 'src/engine/core-modules/actor/actor.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service'; import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver'; import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor'; import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator'; @@ -44,7 +45,11 @@ import { UpdateFieldInput } from './dtos/update-field.input'; TypeORMModule, ActorModule, ], - services: [IsFieldMetadataDefaultValue, FieldMetadataService], + services: [ + IsFieldMetadataDefaultValue, + FieldMetadataService, + FieldMetadataValidationService, + ], resolvers: [ { EntityClass: FieldMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index ac2c5b1d438d..019cfcea7bdf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -56,6 +56,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; +import { FieldMetadataValidationService } from './field-metadata-validation.service'; import { FieldMetadataEntity, FieldMetadataType, @@ -82,6 +83,7 @@ export class FieldMetadataService extends TypeOrmQueryService( + fieldMetadataInput.type, fieldMetadataInput, objectMetadata, ); @@ -391,6 +394,7 @@ export class FieldMetadataService extends TypeOrmQueryService( + existingFieldMetadata.type, fieldMetadataInput, objectMetadata, ); @@ -707,7 +711,11 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T, objectMetadata: ObjectMetadataEntity): T { + >( + fieldMetadataType: FieldMetadataType, + fieldMetadataInput: T, + objectMetadata: ObjectMetadataEntity, + ): T { if (fieldMetadataInput.name) { try { validateFieldNameValidityOrThrow(fieldMetadataInput.name); @@ -748,6 +756,13 @@ export class FieldMetadataService extends TypeOrmQueryService Date: Fri, 4 Oct 2024 11:41:05 +0200 Subject: [PATCH 04/16] Add Skeleton loading for side panel (#7394) This PR was created by [GitStart](https://gitstart.com/) to address the requirements from this ticket: [TWNTY-7112](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-7112). --- ### Description - To test you can use `await new Promise(r => setTimeout(r, 5000));` line 74 of \`openCreateActivityDrawer.ts\` - We added a recoil state to define the loading status - Design points to note: 1 - We did not change the chip component styles because would be unrelated to the issue can you confirm if you still need this change? ![](https://assets-service.gitstart.com/28455/c5999ef1-a7fc-4c53-b425-d0588092ba90.png) 2- In Figma, the loading state shows the Chip rendering an initial name before showing the loaded name, currently, we are rendering the correct name while loading, the change that makes this possible is below ![](https://assets-service.gitstart.com/28455/a0e14045-9a14-4d19-9570-62781fba1aa4.png) if we set it as null, the initial name would appear, but also the previous data in the state would affect the UI, passing the `activityObjectNameSingular` data allows us to clear the previous data, and make the Chip instantly updated, let us know if this behavior is fine, or if you still want an initial name to be rendered while is loading. 3 - Currently, the loading state of the tabs does not affect the selected tab (auto-defined by the component) should we change this logic for all Tabs used in the app, or make this behavior optional by using props? ![](https://assets-service.gitstart.com/28455/223c2e9f-3f4b-4107-b40d-f98a95266d5d.png) ### Demo ### Refs #7112 --------- Co-authored-by: gitstart-twenty Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Co-authored-by: Marie Stoppa --- .../components/LeftPanelSkeletonLoader.tsx | 6 +- ...ainNavigationDrawerItemsSkeletonLoader.tsx | 14 ++- .../components/RightPanelSkeletonLoader.tsx | 8 +- .../activities/components/SkeletonLoader.tsx | 47 +++++++-- .../hooks/useOpenCreateActivityDrawer.ts | 13 ++- .../components/FavoritesSkeletonLoader.tsx | 18 +++- ...onForObjectMetadataItemsSkeletonLoader.tsx | 16 ++- ...BoardColumnCardContainerSkeletonLoader.tsx | 18 +++- .../RecordInlineCellSkeletonLoader.tsx | 8 +- .../components/PropertyBoxSkeletonLoader.tsx | 13 ++- .../components/RightDrawerRecord.tsx | 9 -- .../states/isNewViewableRecordLoading.ts | 6 ++ .../components/RecordShowContainer.tsx | 99 ++++++++++--------- .../RecordTableCellSkeletonLoader.tsx | 8 +- .../SupportButtonSkeletonLoader.tsx | 5 +- .../skeletons/DropdownMenuSkeletonItem.tsx | 5 +- .../components/RightDrawerTopBar.tsx | 6 ++ .../components/ShowPageActivityContainer.tsx | 10 +- .../components/ShowPageRightContainer.tsx | 7 +- .../components/ShowPageSummaryCard.tsx | 5 +- .../ShowPageSummaryCardSkeletonLoader.tsx | 36 +++++++ ...gationDrawerSectionTitleSkeletonLoader.tsx | 8 +- .../components/ViewBarSkeletonLoader.tsx | 5 +- .../src/pages/auth/PasswordReset.tsx | 3 +- 24 files changed, 273 insertions(+), 100 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx diff --git a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx index 38803c9efecf..c8de2f64c46a 100644 --- a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx @@ -3,6 +3,7 @@ import { motion } from 'framer-motion'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader'; @@ -67,7 +68,10 @@ export const LeftPanelSkeletonLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + diff --git a/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx index 2d660dfda236..bfa360a98070 100644 --- a/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui'; @@ -26,9 +27,18 @@ export const MainNavigationDrawerItemsSkeletonLoader = ({ highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - {title && } + {title && ( + + )} {Array.from({ length }).map((_, index) => ( - + ))} diff --git a/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx index 1c47a6cdcb6c..9e98594369ab 100644 --- a/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { @@ -60,7 +61,10 @@ const StyledSkeletonHeaderLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + ); @@ -73,7 +77,7 @@ const StyledSkeletonAddLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx index 21478ed73c32..0b65ab1143c3 100644 --- a/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx @@ -1,6 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` align-items: center; @@ -25,6 +25,21 @@ const StyledSkeletonSubSectionContent = styled.div` justify-content: center; `; +export const SKELETON_LOADER_HEIGHT_SIZES = { + standard: { + xs: 13, + s: 16, + m: 24, + l: 32, + xl: 40, + }, + columns: { + s: 84, + m: 120, + xxl: 542, + }, +}; + const SkeletonColumnLoader = ({ height }: { height: number }) => { const theme = useTheme(); return ( @@ -55,15 +70,35 @@ export const SkeletonLoader = ({ borderRadius={4} > - + {withSubSections && skeletonItems.map(({ id }, index) => ( - + - - - {index === 1 && } + + + {index === 1 && ( + + )} ))} diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index 400c1f398a95..c35d27837121 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -15,6 +15,7 @@ import { Task } from '@/activities/types/Task'; import { TaskTarget } from '@/activities/types/TaskTarget'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { ActivityTargetableObject } from '../types/ActivityTargetableEntity'; @@ -52,7 +53,9 @@ export const useOpenCreateActivityDrawer = ({ const setViewableRecordNameSingular = useSetRecoilState( viewableRecordNameSingularState, ); - + const setIsNewViewableRecordLoading = useSetRecoilState( + isNewViewableRecordLoadingState, + ); const setIsUpsertingActivityInDB = useSetRecoilState( isUpsertingActivityInDBState, ); @@ -64,6 +67,11 @@ export const useOpenCreateActivityDrawer = ({ targetableObjects: ActivityTargetableObject[]; customAssignee?: WorkspaceMember; }) => { + openRightDrawer(RightDrawerPages.ViewRecord); + setIsNewViewableRecordLoading(true); + setViewableRecordId(null); + setViewableRecordNameSingular(activityObjectNameSingular); + const activity = await createOneActivity({ assigneeId: customAssignee?.id, }); @@ -101,10 +109,9 @@ export const useOpenCreateActivityDrawer = ({ setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); setViewableRecordId(activity.id); - setViewableRecordNameSingular(activityObjectNameSingular); - openRightDrawer(RightDrawerPages.ViewRecord); setIsUpsertingActivityInDB(false); + setIsNewViewableRecordLoading(false); }; return openCreateActivityDrawer; diff --git a/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx b/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx index 3822a12bc9b6..5e5d1ea7f8ec 100644 --- a/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` display: flex; @@ -25,10 +26,19 @@ export const FavoritesSkeletonLoader = () => { borderRadius={4} > - + - - + + diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx index 70e00d717330..70b965b1827f 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; @@ -20,9 +21,18 @@ export const NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader: React. borderRadius={4} > - - - + + + ); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx index 4aeafd96f1fd..afaf8e9d78f4 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx @@ -1,7 +1,8 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { StyledBoardCardBody, StyledBoardCardHeader, @@ -43,7 +44,10 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({ > - + @@ -51,8 +55,14 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({ skeletonItems.map(({ id }) => ( - - + + ))} diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx index 2e673478dbd5..bd912d7b55bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { StyledSkeletonDiv } from './RecordInlineCellContainer'; export const RecordInlineCellSkeletonLoader = () => { @@ -13,7 +14,10 @@ export const RecordInlineCellSkeletonLoader = () => { borderRadius={4} > - + ); diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx index 38ee35d74787..88a499952c1f 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonDiv = styled.div` align-items: center; @@ -22,8 +23,14 @@ export const PropertyBoxSkeletonLoader = () => { > {skeletonItems.map(({ id }) => ( - - + + ))} diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx index e68ce3883ef0..6a76206e6e75 100644 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx @@ -19,15 +19,6 @@ export const RightDrawerRecord = () => { viewableRecordNameSingularState, ); const viewableRecordId = useRecoilValue(viewableRecordIdState); - - if (!viewableRecordNameSingular) { - throw new Error(`Object name is not defined`); - } - - if (!viewableRecordId) { - throw new Error(`Record id is not defined`); - } - const { objectNameSingular, objectRecordId } = useRecordShowPage( viewableRecordNameSingular ?? '', viewableRecordId ?? '', diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts new file mode 100644 index 000000000000..904677204cc6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isNewViewableRecordLoadingState = createState({ + key: 'activities/is-new-viewable-record-loading', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx index 5f23472fe180..e72df975f22f 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx @@ -21,6 +21,7 @@ import { RecordInlineCell } from '@/object-record/record-inline-cell/components/ import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection'; import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection'; import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState'; @@ -33,6 +34,7 @@ import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer'; import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer'; import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard'; +import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { FieldMetadataType, @@ -80,7 +82,9 @@ export const RecordShowContainer = ({ recordId: objectRecordId, }), ); - + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); const [uploadImage] = useUploadImageMutation(); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular }); @@ -162,53 +166,56 @@ export const RecordShowContainer = ({ const isReadOnly = objectMetadataItem.isRemote; const isMobile = useIsMobile() || isInRightDrawer; const isPrefetchLoading = useIsPrefetchLoading(); + const isNewRightDrawerItemLoading = + isInRightDrawer && isNewViewableRecordLoading; - const summaryCard = isDefined(recordFromStore) ? ( - - - - } - avatarType={recordIdentifier?.avatarType ?? 'rounded'} - onUploadPicture={ - objectNameSingular === 'person' ? onUploadPicture : undefined - } - /> - ) : ( - <> - ); + useUpdateRecord: useUpdateOneObjectRecordMutation, + hotkeyScope: InlineCellHotkeyScope.InlineCell, + isCentered: true, + }} + > + + + } + avatarType={recordIdentifier?.avatarType ?? 'rounded'} + onUploadPicture={ + objectNameSingular === 'person' ? onUploadPicture : undefined + } + /> + ) : ( + + ); const fieldsBox = ( <> diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx index b010bdaecd6d..f62018d33388 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` padding-left: ${({ theme }) => theme.spacing(2)}; @@ -15,7 +16,10 @@ const StyledRecordTableCellLoader = ({ width }: { width?: number }) => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx b/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx index 86d4d1b93231..78440fe021d2 100644 --- a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx @@ -1,5 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; export const SupportButtonSkeletonLoader = () => { const theme = useTheme(); @@ -9,7 +10,7 @@ export const SupportButtonSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx index f4853bc48c69..820831f7c77b 100644 --- a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx +++ b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledDropdownMenuSkeletonContainer = styled.div` --horizontal-padding: ${({ theme }) => theme.spacing(1)}; @@ -21,7 +22,7 @@ export const DropdownMenuSkeletonItem = () => { baseColor={theme.background.quaternary} highlightColor={theme.background.secondary} > - + ); diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx index 9176af20340a..b80b943e1730 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx @@ -5,6 +5,7 @@ import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton'; @@ -66,6 +67,10 @@ export const RightDrawerTopBar = () => { viewableRecordNameSingularState, ); + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + const viewableRecordId = useRecoilValue(viewableRecordIdState); const { objectMetadataItem } = useObjectMetadataItem({ @@ -95,6 +100,7 @@ export const RightDrawerTopBar = () => { > {!isRightDrawerMinimized && ( } size={ChipSize.Large} diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx index a69f54528cba..2e38456c4fb9 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx @@ -1,8 +1,10 @@ import { RichTextEditor } from '@/activities/components/RichTextEditor'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; const StyledShowPageActivityContainer = styled.div` margin-top: ${({ theme }) => theme.spacing(6)}; @@ -16,7 +18,11 @@ export const ShowPageActivityContainer = ({ 'targetObjectNameSingular' | 'id' >; }) => { - return ( + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + + return !isNewViewableRecordLoading ? ( + ) : ( + <> ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx index c06129296187..dab0a2104e12 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx @@ -7,6 +7,7 @@ import { TimelineActivities } from '@/activities/timelineActivities/components/T import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { Button } from '@/ui/input/button/components/Button'; @@ -135,6 +136,10 @@ export const ShowPageRightContainer = ({ const isMobile = useIsMobile(); + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + const tabs = [ { id: 'richText', @@ -272,7 +277,7 @@ export const ShowPageRightContainer = ({ diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 9627be5d7e61..dca1b2cea957 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { ChangeEvent, ReactNode, useRef } from 'react'; @@ -88,9 +89,9 @@ const StyledShowPageSummaryCardSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + - + ); diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx new file mode 100644 index 000000000000..51b15f506c67 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx @@ -0,0 +1,36 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; + +const StyledContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(4)}; + height: ${({ theme }) => theme.spacing(19)}; + margin: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledRectangularSkeleton = styled(Skeleton)` + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(24)}; + margin: ${({ theme }) => theme.spacing(1)}; + border-radius: ${({ theme }) => theme.border.radius.sm}; +`; + +export const ShowPageSummaryCardSkeletonLoader = () => { + const theme = useTheme(); + return ( + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx index fcd3ffe20eaa..5391ea3e320d 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonTitle = styled.div` margin-bottom: ${(props) => props.theme.spacing(2)}; @@ -16,7 +17,10 @@ export const NavigationDrawerSectionTitleSkeletonLoader = () => { borderRadius={4} > - + ); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx b/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx index a7d0c1b247c1..a82565afa813 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx @@ -1,5 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; export const ViewBarSkeletonLoader = () => { const theme = useTheme(); @@ -9,7 +10,7 @@ export const ViewBarSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index 79ea7074021d..11cf5d016b42 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -10,6 +10,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import { z } from 'zod'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { Logo } from '@/auth/components/Logo'; import { Title } from '@/auth/components/Title'; import { useAuth } from '@/auth/hooks/useAuth'; @@ -174,7 +175,7 @@ export const PasswordReset = () => { highlightColor={theme.background.secondary} > Date: Fri, 4 Oct 2024 11:58:33 +0200 Subject: [PATCH 05/16] Refactor graphql query runner and add mutation resolvers (#7418) Fixes https://github.com/twentyhq/twenty/issues/6859 This PR adds all the remaining resolvers for - updateOne/updateMany - createOne/createMany - deleteOne/deleteMany - destroyOne - restoreMany Also - refactored the graphql-query-runner to be able to add other resolvers without too much boilerplate. - add missing events that were not sent anymore as well as webhooks - make resolver injectable so they can inject other services as well - use objectMetadataMap from cache instead of computing it multiple time - various fixes (mutation not correctly parsing JSON, relationHelper fetching data with empty ids set, ...) Next steps: - Wrapping query builder to handle DB events properly - Move webhook emitters to db event listener - Add pagination where it's missing (findDuplicates, nested relations, etc...) --- .../1726848397026-addTypeOrmMetadata.ts | 0 .../migrations/1725893697807-addIndexType.ts | 8 +- .../graphql-query-resolver.factory.ts | 51 ++ .../graphql-query-filter-condition.parser.ts | 2 +- .../graphql-query-filter-field.parser.ts | 3 - .../graphql-query.parser.ts | 14 +- .../graphql-query-runner.module.ts | 29 +- .../graphql-query-runner.service.ts | 533 +++++++++--------- ...t-records-to-graphql-connection.helper.ts} | 2 +- .../process-nested-relations.helper.ts | 260 +++++---- .../interfaces/resolver-service.interface.ts | 12 + ...phql-query-create-many-resolver.service.ts | 103 ++-- ...phql-query-destroy-one-resolver.service.ts | 57 +- ...-query-find-duplicates-resolver.service.ts | 214 +++++++ ...raphql-query-find-many-resolver.service.ts | 128 ++--- ...graphql-query-find-one-resolver.service.ts | 77 +-- .../graphql-query-search-resolver.service.ts | 84 ++- ...phql-query-update-many-resolver.service.ts | 116 ++++ ...aphql-query-update-one-resolver.service.ts | 123 ++++ .../services/api-event-emitter.service.ts | 137 +++++ .../utils/cursors.util.ts | 23 + .../query-runner-option.interface.ts | 6 + .../types/workspace-query-hook.type.ts | 5 +- .../factories/create-many-resolver.factory.ts | 5 +- .../factories/create-one-resolver.factory.ts | 13 +- .../factories/delete-many-resolver.factory.ts | 24 +- .../factories/delete-one-resolver.factory.ts | 24 +- .../destroy-many-resolver.factory.ts | 12 +- .../factories/destroy-one-resolver.factory.ts | 9 +- .../find-duplicates-resolver.factory.ts | 30 +- .../factories/find-many-resolver.factory.ts | 5 +- .../factories/find-one-resolver.factory.ts | 5 +- .../restore-many-resolver.factory.ts | 30 +- .../factories/search-resolver-factory.ts | 9 +- .../factories/update-many-resolver.factory.ts | 24 +- .../factories/update-one-resolver.factory.ts | 24 +- .../workspace-resolver.factory.ts | 10 +- ...kspace-schema-builder-context.interface.ts | 8 +- .../api/graphql/workspace-schema.factory.ts | 1 + .../duplicate/duplicate.service.ts | 80 +-- .../index-metadata/index-metadata.entity.ts | 2 +- .../twenty-orm/utils/format-data.util.ts | 91 +-- .../twenty-orm/utils/format-result.util.ts | 24 + 43 files changed, 1669 insertions(+), 748 deletions(-) rename packages/twenty-server/src/database/typeorm/{metadata => core}/migrations/1726848397026-addTypeOrmMetadata.ts (100%) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts rename packages/twenty-server/src/engine/api/graphql/graphql-query-runner/{orm-mappers/object-records-to-graphql-connection.mapper.ts => helpers/object-records-to-graphql-connection.helper.ts} (99%) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726848397026-addTypeOrmMetadata.ts b/packages/twenty-server/src/database/typeorm/core/migrations/1726848397026-addTypeOrmMetadata.ts similarity index 100% rename from packages/twenty-server/src/database/typeorm/metadata/migrations/1726848397026-addTypeOrmMetadata.ts rename to packages/twenty-server/src/database/typeorm/core/migrations/1726848397026-addTypeOrmMetadata.ts diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts index 41edac1a5990..59a1828627ea 100644 --- a/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts @@ -5,12 +5,12 @@ export class AddIndexType1725893697807 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TYPE metadata."indextype_enum" AS ENUM ('BTREE', 'GIN')`, + `CREATE TYPE "metadata"."indexMetadata_indextype_enum" AS ENUM('BTREE', 'GIN')`, ); await queryRunner.query(` ALTER TABLE metadata."indexMetadata" - ADD COLUMN "indexType" metadata."indextype_enum" NOT NULL DEFAULT 'BTREE'; + ADD COLUMN "indexType" metadata."indexMetadata_indextype_enum" NOT NULL DEFAULT 'BTREE'; `); } @@ -19,6 +19,8 @@ export class AddIndexType1725893697807 implements MigrationInterface { ALTER TABLE metadata."indexMetadata" DROP COLUMN "indexType" `); - await queryRunner.query(`DROP TYPE metadata."indextype_enum"`); + await queryRunner.query( + `DROP TYPE metadata."indexMetadata_indextype_enum"`, + ); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts new file mode 100644 index 000000000000..ca2506d18aa9 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + ResolverArgs, + WorkspaceResolverBuilderMethodNames, +} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; +import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; +import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; +import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; +import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; + +@Injectable() +export class GraphqlQueryResolverFactory { + constructor(private moduleRef: ModuleRef) {} + + public getResolver( + operationName: WorkspaceResolverBuilderMethodNames, + ): ResolverService { + switch (operationName) { + case 'findOne': + return this.moduleRef.get(GraphqlQueryFindOneResolverService); + case 'findMany': + return this.moduleRef.get(GraphqlQueryFindManyResolverService); + case 'findDuplicates': + return this.moduleRef.get(GraphqlQueryFindDuplicatesResolverService); + case 'search': + return this.moduleRef.get(GraphqlQuerySearchResolverService); + case 'createOne': + case 'createMany': + return this.moduleRef.get(GraphqlQueryCreateManyResolverService); + case 'destroyOne': + return this.moduleRef.get(GraphqlQueryDestroyOneResolverService); + case 'updateOne': + case 'deleteOne': + return this.moduleRef.get(GraphqlQueryUpdateOneResolverService); + case 'updateMany': + case 'deleteMany': + case 'restoreMany': + return this.moduleRef.get(GraphqlQueryUpdateManyResolverService); + default: + throw new Error(`Unsupported operation: ${operationName}`); + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts index 093364db8479..21f9bdbdccb0 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts @@ -25,7 +25,7 @@ export class GraphqlQueryFilterConditionParser { public parse( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - filter: RecordFilter, + filter: Partial, ): SelectQueryBuilder { if (!filter || Object.keys(filter).length === 0) { return queryBuilder; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index fe37c4d445da..920fa01c56d6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -58,7 +58,6 @@ export class GraphqlQueryFilterFieldParser { } const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, operator, objectNameSingular, key, @@ -73,7 +72,6 @@ export class GraphqlQueryFilterFieldParser { } private computeWhereConditionParts( - fieldMetadata: FieldMetadataInterface, operator: string, objectNameSingular: string, key: string, @@ -185,7 +183,6 @@ export class GraphqlQueryFilterFieldParser { ); const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, operator, objectNameSingular, fullFieldName, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index d83667211bd0..0aa047fc31f7 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -9,7 +9,6 @@ import { RecordFilter, RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser'; import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser'; @@ -17,6 +16,7 @@ import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql import { FieldMetadataMap, ObjectMetadataMap, + ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export class GraphqlQueryParser { @@ -39,10 +39,10 @@ export class GraphqlQueryParser { ); } - applyFilterToBuilder( + public applyFilterToBuilder( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - recordFilter: RecordFilter, + recordFilter: Partial, ): SelectQueryBuilder { return this.filterConditionParser.parse( queryBuilder, @@ -51,7 +51,7 @@ export class GraphqlQueryParser { ); } - applyDeletedAtToBuilder( + public applyDeletedAtToBuilder( queryBuilder: SelectQueryBuilder, recordFilter: RecordFilter, ): SelectQueryBuilder { @@ -88,7 +88,7 @@ export class GraphqlQueryParser { return false; }; - applyOrderToBuilder( + public applyOrderToBuilder( queryBuilder: SelectQueryBuilder, orderBy: RecordOrderBy, objectNameSingular: string, @@ -103,8 +103,8 @@ export class GraphqlQueryParser { return queryBuilder.orderBy(parsedOrderBys as OrderByCondition); } - parseSelectedFields( - parentObjectMetadata: ObjectMetadataInterface, + public parseSelectedFields( + parentObjectMetadata: ObjectMetadataMapItem, graphqlSelectedFields: Partial>, ): { select: Record; relations: Record } { const parentFields = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts index 28cb362ca3f0..8c689ff8a979 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts @@ -1,16 +1,43 @@ import { Module } from '@nestjs/common'; +import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; +import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; +import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; +import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; +import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; +import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; +import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module'; import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; + +const graphqlQueryResolvers = [ + GraphqlQueryFindOneResolverService, + GraphqlQueryFindManyResolverService, + GraphqlQueryFindDuplicatesResolverService, + GraphqlQueryCreateManyResolverService, + GraphqlQueryDestroyOneResolverService, + GraphqlQueryUpdateOneResolverService, + GraphqlQueryUpdateManyResolverService, + GraphqlQuerySearchResolverService, +]; + @Module({ imports: [ WorkspaceQueryHookModule, WorkspaceQueryRunnerModule, FeatureFlagModule, ], - providers: [GraphqlQueryRunnerService], + providers: [ + GraphqlQueryRunnerService, + GraphqlQueryResolverFactory, + ApiEventEmitterService, + ...graphqlQueryResolvers, + ], exports: [GraphqlQueryRunnerService], }) export class GraphqlQueryRunnerModule {} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts index 8c866695b6ad..4e3475927ecd 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts @@ -6,285 +6,377 @@ import { RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; +import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, CreateOneResolverArgs, + DeleteManyResolverArgs, + DeleteOneResolverArgs, DestroyOneResolverArgs, + FindDuplicatesResolverArgs, FindManyResolverArgs, FindOneResolverArgs, + ResolverArgs, ResolverArgsType, + RestoreManyResolverArgs, SearchResolverArgs, + UpdateManyResolverArgs, + UpdateOneResolverArgs, + WorkspaceResolverBuilderMethodNames, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; -import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; -import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; -import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; -import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; +import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { CallWebhookJobsJob, CallWebhookJobsJobData, CallWebhookJobsJobOperation, } from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job'; -import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; -import { - WorkspaceQueryRunnerException, - WorkspaceQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; -import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; -import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; +import { capitalize } from 'src/utils/capitalize'; @Injectable() export class GraphqlQueryRunnerService { constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly featureFlagService: FeatureFlagService, private readonly workspaceQueryHookService: WorkspaceQueryHookService, private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, @InjectMessageQueue(MessageQueue.webhookQueue) private readonly messageQueueService: MessageQueueService, + private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory, + private readonly apiEventEmitterService: ApiEventEmitterService, ) {} + /** QUERIES */ + @LogExecutionTime() - async findOne< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - >( + async findOne( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryFindOneResolverService = - new GraphqlQueryFindOneResolverService(this.twentyORMGlobalManager); - - const { authContext, objectMetadataItem } = options; - - if (!args.filter || Object.keys(args.filter).length === 0) { - throw new WorkspaceQueryRunnerException( - 'Missing filter argument', - WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findOne', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, + ): Promise { + return this.executeQuery, ObjectRecord>( + 'findOne', + args, options, - ResolverArgsType.FindOne, - )) as FindOneResolverArgs; - - return graphqlQueryFindOneResolverService.findOne(computedArgs, options); + ); } @LogExecutionTime() async findMany< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - OrderBy extends RecordOrderBy = RecordOrderBy, + ObjectRecord extends IRecord, + Filter extends RecordFilter, + OrderBy extends RecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, + ): Promise>> { + return this.executeQuery< + FindManyResolverArgs, + IConnection> + >('findMany', args, options); + } + + @LogExecutionTime() + async findDuplicates( + args: FindDuplicatesResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise[]> { + return this.executeQuery< + FindDuplicatesResolverArgs>, + IConnection[] + >('findDuplicates', args, options); + } + + @LogExecutionTime() + async search( + args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, ): Promise> { - const graphqlQueryFindManyResolverService = - new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager); + return this.executeQuery>( + 'search', + args, + options, + ); + } - const { authContext, objectMetadataItem } = options; + /** MUTATIONS */ - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findMany', - args, + @LogExecutionTime() + async createOne( + args: CreateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const results = await this.executeQuery< + CreateManyResolverArgs>, + ObjectRecord[] + >('createMany', { data: [args.data], upsert: args.upsert }, options); + + // TODO: emitCreateEvents should be moved to the ORM layer + if (results) { + this.apiEventEmitterService.emitCreateEvents( + results, + options.authContext, + options.objectMetadataItem, ); + } - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.FindMany, - )) as FindManyResolverArgs; - - return graphqlQueryFindManyResolverService.findMany(computedArgs, options); + return results[0]; } @LogExecutionTime() - async createOne( - args: CreateOneResolverArgs>, + async createMany( + args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise { + const results = await this.executeQuery< + CreateManyResolverArgs>, + ObjectRecord[] + >('createMany', args, options); + + if (results) { + this.apiEventEmitterService.emitCreateEvents( + results, + options.authContext, + options.objectMetadataItem, + ); + } - const { authContext, objectMetadataItem } = options; + return results; + } - assertMutationNotOnRemoteObject(objectMetadataItem); + @LogExecutionTime() + public async updateOne( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const existingRecord = await this.executeQuery< + FindOneResolverArgs, + ObjectRecord + >( + 'findOne', + { + filter: { id: { eq: args.id } }, + }, + options, + ); - if (args.data.id) { - assertIsValidUuid(args.data.id); - } + const result = await this.executeQuery< + UpdateOneResolverArgs>, + ObjectRecord + >('updateOne', args, options); - const createManyArgs = { - data: [args.data], - upsert: args.upsert, - } as CreateManyResolverArgs; + this.apiEventEmitterService.emitUpdateEvents( + [existingRecord], + [result], + Object.keys(args.data), + options.authContext, + options.objectMetadataItem, + ); - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'createMany', - createManyArgs, - ); + return result; + } - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, + @LogExecutionTime() + public async updateMany( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const existingRecords = await this.executeQuery< + FindManyResolverArgs, + IConnection> + >( + 'findMany', + { + filter: args.filter, + }, options, - ResolverArgsType.CreateMany, - )) as CreateManyResolverArgs; + ); + + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord[] + >('updateMany', args, options); + + this.apiEventEmitterService.emitUpdateEvents( + existingRecords.edges.map((edge) => edge.node), + result, + Object.keys(args.data), + options.authContext, + options.objectMetadataItem, + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, + return result; + } + + @LogExecutionTime() + public async deleteOne( + args: DeleteOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const result = await this.executeQuery< + UpdateOneResolverArgs>, + ObjectRecord + >( + 'deleteOne', + { + id: args.id, + data: { deletedAt: new Date() } as Partial, + }, options, - )) as ObjectRecord[]; + ); + + this.apiEventEmitterService.emitDeletedEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); - await this.triggerWebhooks( - results, - CallWebhookJobsJobOperation.create, + return result; + } + + @LogExecutionTime() + public async deleteMany( + args: DeleteManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord[] + >( + 'deleteMany', + { + filter: args.filter, + + data: { deletedAt: new Date() } as Partial, + }, options, ); - this.emitCreateEvents( - results, - authContext, - objectMetadataItem, + this.apiEventEmitterService.emitDeletedEvents( + result, + options.authContext, + options.objectMetadataItem, ); - return results?.[0] as ObjectRecord; + return result; } @LogExecutionTime() - async search( - args: SearchResolverArgs, + async destroyOne( + args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise> { - const graphqlQuerySearchResolverService = - new GraphqlQuerySearchResolverService( - this.twentyORMGlobalManager, - this.featureFlagService, - ); + ): Promise { + const result = await this.executeQuery< + DestroyOneResolverArgs, + ObjectRecord + >('destroyOne', args, options); + + this.apiEventEmitterService.emitDestroyEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); - return graphqlQuerySearchResolverService.search(args, options); + return result; } @LogExecutionTime() - async createMany( - args: CreateManyResolverArgs>, + public async restoreMany( + args: RestoreManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise { + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord + >( + 'restoreMany', + { + filter: args.filter, + data: { deletedAt: null } as Partial, + }, + options, + ); + + return result; + } + private async executeQuery( + operationName: WorkspaceResolverBuilderMethodNames, + args: Input, + options: WorkspaceQueryRunnerOptions, + ): Promise { const { authContext, objectMetadataItem } = options; - assertMutationNotOnRemoteObject(objectMetadataItem); + const resolver = + this.graphqlQueryResolverFactory.getResolver(operationName); - args.data.forEach((record) => { - if (record?.id) { - assertIsValidUuid(record.id); - } - }); + await resolver.validate(args, options); const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( authContext, objectMetadataItem.nameSingular, - 'createMany', + operationName, args, ); - const computedArgs = (await this.queryRunnerArgsFactory.create( + const computedArgs = await this.queryRunnerArgsFactory.create( hookedArgs, options, - ResolverArgsType.CreateMany, - )) as CreateManyResolverArgs; + ResolverArgsType[capitalize(operationName)], + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, - options, - )) as ObjectRecord[]; + const results = await resolver.resolve(computedArgs as Input, options); await this.workspaceQueryHookService.executePostQueryHooks( authContext, objectMetadataItem.nameSingular, - 'createMany', - results, + operationName, + Array.isArray(results) ? results : [results], ); - await this.triggerWebhooks( - results, - CallWebhookJobsJobOperation.create, - options, - ); + const jobOperation = this.operationNameToJobOperation(operationName); - this.emitCreateEvents( - results, - authContext, - objectMetadataItem, - ); + if (jobOperation) { + await this.triggerWebhooks(results, jobOperation, options); + } return results; } - private emitCreateEvents( - records: BaseRecord[], - authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, - ) { - this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.created`, - records.map( - (record) => - ({ - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - after: record, - }, - }) satisfies ObjectRecordCreateEvent, - ), - authContext.workspace.id, - ); + private operationNameToJobOperation( + operationName: WorkspaceResolverBuilderMethodNames, + ): CallWebhookJobsJobOperation | undefined { + switch (operationName) { + case 'createOne': + case 'createMany': + return CallWebhookJobsJobOperation.create; + case 'updateOne': + case 'updateMany': + case 'restoreMany': + return CallWebhookJobsJobOperation.update; + case 'deleteOne': + case 'deleteMany': + return CallWebhookJobsJobOperation.delete; + case 'destroyOne': + return CallWebhookJobsJobOperation.destroy; + default: + return undefined; + } } - private async triggerWebhooks( - jobsData: Record[] | undefined, + private async triggerWebhooks( + jobsData: T[] | undefined, operation: CallWebhookJobsJobOperation, options: WorkspaceQueryRunnerOptions, - ) { - if (!Array.isArray(jobsData)) { - return; - } + ): Promise { + if (!jobsData || !Array.isArray(jobsData)) return; + jobsData.forEach((jobData) => { this.messageQueueService.add( CallWebhookJobsJob.name, @@ -298,99 +390,4 @@ export class GraphqlQueryRunnerService { ); }); } - - @LogExecutionTime() - async destroyOne( - args: DestroyOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryDestroyOneResolverService = - new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager); - - const { authContext, objectMetadataItem } = options; - - assertMutationNotOnRemoteObject(objectMetadataItem); - assertIsValidUuid(args.id); - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'destroyOne', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.DestroyOne, - )) as DestroyOneResolverArgs; - - const result = (await graphqlQueryDestroyOneResolverService.destroyOne( - computedArgs, - options, - )) as ObjectRecord; - - await this.workspaceQueryHookService.executePostQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'destroyOne', - [result], - ); - - await this.triggerWebhooks( - [result], - CallWebhookJobsJobOperation.destroy, - options, - ); - - this.emitDestroyEvents([result], authContext, objectMetadataItem); - - return result; - } - - private emitDestroyEvents( - records: BaseRecord[], - authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, - ) { - this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.destroyed`, - records.map((record) => { - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: this.removeNestedProperties(record), - }, - } satisfies ObjectRecordDeleteEvent; - }), - authContext.workspace.id, - ); - } - - private removeNestedProperties( - record: Record, - ) { - if (!record) { - return; - } - - const sanitizedRecord = {}; - - for (const [key, value] of Object.entries(record)) { - if (value && typeof value === 'object' && value['edges']) { - continue; - } - - if (key === '__typename') { - continue; - } - - sanitizedRecord[key] = value; - } - - return sanitizedRecord; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts similarity index 99% rename from packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts rename to packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index b9a81ef245dc..5ccff3af114d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -20,7 +20,7 @@ import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspac import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isPlainObject } from 'src/utils/is-plain-object'; -export class ObjectRecordsToGraphqlConnectionMapper { +export class ObjectRecordsToGraphqlConnectionHelper { private objectMetadataMap: ObjectMetadataMap; constructor(objectMetadataMap: ObjectMetadataMap) { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index f19c7cf06eb3..dd3e5abd4020 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -4,6 +4,7 @@ import { FindOptionsRelations, In, ObjectLiteral, + Repository, } from 'typeorm'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; @@ -16,17 +17,38 @@ import { ObjectMetadataMap, ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util'; export class ProcessNestedRelationsHelper { - private readonly twentyORMGlobalManager: TwentyORMGlobalManager; + constructor() {} - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; + public async processNestedRelations( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relations: Record>, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise { + const processRelationTasks = Object.entries(relations).map( + ([relationName, nestedRelations]) => + this.processRelation( + objectMetadataMap, + parentObjectMetadataItem, + parentObjectRecords, + relationName, + nestedRelations, + limit, + authContext, + dataSource, + ), + ); + + await Promise.all(processRelationTasks); } - private async processFromRelation( + private async processRelation( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, parentObjectRecords: ObjectRecord[], @@ -35,49 +57,71 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { + ): Promise { const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); - - const inverseRelationName = - objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[ - relationMetadata.toFieldMetadataId - ]?.name; - - const referenceObjectMetadata = getRelationObjectMetadata( + const relationDirection = deduceRelationDirection( relationFieldMetadata, - objectMetadataMap, + relationMetadata, ); - const referenceObjectMetadataName = referenceObjectMetadata.nameSingular; + const processor = + relationDirection === 'to' + ? this.processToRelation + : this.processFromRelation; - const relationRepository = await dataSource.getRepository( - referenceObjectMetadataName, + await processor.call( + this, + objectMetadataMap, + parentObjectMetadataItem, + parentObjectRecords, + relationName, + nestedRelations, + limit, + authContext, + dataSource, ); + } - const relationIds = parentObjectRecords.map((item) => item.id); - - const uniqueRelationIds = [...new Set(relationIds)]; - - const relationFindOptions: FindManyOptions = { - where: { - [`${inverseRelationName}Id`]: In(uniqueRelationIds), - }, - take: limit * parentObjectRecords.length, - }; + private async processFromRelation( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relationName: string, + nestedRelations: any, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise { + const { inverseRelationName, referenceObjectMetadata } = + this.getRelationMetadata( + objectMetadataMap, + parentObjectMetadataItem, + relationName, + ); + const relationRepository = dataSource.getRepository( + referenceObjectMetadata.nameSingular, + ); - const relationResults = await relationRepository.find(relationFindOptions); + const relationIds = this.getUniqueIds(parentObjectRecords, 'id'); + const relationResults = await this.findRelations( + relationRepository, + inverseRelationName, + relationIds, + limit * parentObjectRecords.length, + ); - parentObjectRecords.forEach((item) => { - (item as any)[relationName] = relationResults.filter( - (rel) => rel[`${inverseRelationName}Id`] === item.id, - ); - }); + this.assignRelationResults( + parentObjectRecords, + relationResults, + relationName, + `${inverseRelationName}Id`, + ); if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( objectMetadataMap, - objectMetadataMap[referenceObjectMetadataName], + objectMetadataMap[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -96,48 +140,37 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { - const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; - - const referenceObjectMetadata = getRelationObjectMetadata( - relationFieldMetadata, + ): Promise { + const { referenceObjectMetadata } = this.getRelationMetadata( objectMetadataMap, + parentObjectMetadataItem, + relationName, ); - - const referenceObjectMetadataName = referenceObjectMetadata.nameSingular; - const relationRepository = dataSource.getRepository( - referenceObjectMetadataName, + referenceObjectMetadata.nameSingular, ); - const relationIds = parentObjectRecords.map( - (item) => item[`${relationName}Id`], + const relationIds = this.getUniqueIds( + parentObjectRecords, + `${relationName}Id`, + ); + const relationResults = await this.findRelations( + relationRepository, + 'id', + relationIds, + limit, ); - const uniqueRelationIds = [...new Set(relationIds)]; - - const relationFindOptions: FindManyOptions = { - where: { - id: In(uniqueRelationIds), - }, - take: limit, - }; - - const relationResults = await relationRepository.find(relationFindOptions); - - parentObjectRecords.forEach((item) => { - if (relationResults.length === 0) { - (item as any)[`${relationName}Id`] = null; - } - (item as any)[relationName] = relationResults.filter( - (rel) => rel.id === item[`${relationName}Id`], - )[0]; - }); + this.assignToRelationResults( + parentObjectRecords, + relationResults, + relationName, + ); if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( objectMetadataMap, - objectMetadataMap[referenceObjectMetadataName], + objectMetadataMap[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -147,48 +180,71 @@ export class ProcessNestedRelationsHelper { } } - public async processNestedRelations( + private getRelationMetadata( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], - relations: Record>, - limit: number, - authContext: any, - dataSource: DataSource, + relationName: string, ) { - for (const [relationName, nestedRelations] of Object.entries(relations)) { - const relationFieldMetadata = - parentObjectMetadataItem.fields[relationName]; - const relationMetadata = getRelationMetadata(relationFieldMetadata); - - const relationDirection = deduceRelationDirection( - relationFieldMetadata, - relationMetadata, + const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; + const relationMetadata = getRelationMetadata(relationFieldMetadata); + const referenceObjectMetadata = getRelationObjectMetadata( + relationFieldMetadata, + objectMetadataMap, + ); + const inverseRelationName = + objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[ + relationMetadata.toFieldMetadataId + ]?.name; + + return { inverseRelationName, referenceObjectMetadata }; + } + + private getUniqueIds(records: IRecord[], idField: string): any[] { + return [...new Set(records.map((item) => item[idField]))]; + } + + private async findRelations( + repository: Repository, + field: string, + ids: any[], + limit: number, + ): Promise { + if (ids.length === 0) { + return []; + } + const findOptions: FindManyOptions = { + where: { [field]: In(ids) }, + take: limit, + }; + + return repository.find(findOptions); + } + + private assignRelationResults( + parentRecords: IRecord[], + relationResults: any[], + relationName: string, + joinField: string, + ): void { + parentRecords.forEach((item) => { + (item as any)[relationName] = relationResults.filter( + (rel) => rel[joinField] === item.id, ); + }); + } - if (relationDirection === 'to') { - await this.processToRelation( - objectMetadataMap, - parentObjectMetadataItem, - parentObjectRecords, - relationName, - nestedRelations, - limit, - authContext, - dataSource, - ); - } else { - await this.processFromRelation( - objectMetadataMap, - parentObjectMetadataItem, - parentObjectRecords, - relationName, - nestedRelations, - limit, - authContext, - dataSource, - ); + private assignToRelationResults( + parentRecords: IRecord[], + relationResults: any[], + relationName: string, + ): void { + parentRecords.forEach((item) => { + if (relationResults.length === 0) { + (item as any)[`${relationName}Id`] = null; } - } + (item as any)[relationName] = + relationResults.find((rel) => rel.id === item[`${relationName}Id`]) ?? + null; + }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts new file mode 100644 index 000000000000..f88691647425 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts @@ -0,0 +1,12 @@ +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; + +export interface ResolverService { + resolve: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise; + validate: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise; +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index f66497a6812a..aa8a81ce85a6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -1,51 +1,53 @@ +import { Injectable } from '@nestjs/common'; + import graphqlFields from 'graphql-fields'; import { In, InsertResult } from 'typeorm'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryCreateManyResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryCreateManyResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async createMany( + async resolve( args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataItem, objectMetadataCollection, info } = + ): Promise { + const { authContext, info, objectMetadataMap, objectMetadataMapItem } = options; - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, ); + const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const selectedFields = graphqlFields(info); - const { select, relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, selectedFields, ); @@ -56,24 +58,59 @@ export class GraphqlQueryCreateManyResolverService { skipUpdateIfNoValuesChanged: true, }); - const upsertedRecords = await repository.find({ - where: { + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const nonFormattedUpsertedRecords = (await queryBuilder + .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)), - }, - select, - relations, - }); + }) + .take(QUERY_MAX_RECORDS) + .getMany()) as ObjectRecord[]; + + const upsertedRecords = formatResult( + nonFormattedUpsertedRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + upsertedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return upsertedRecords.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord( record, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, 1, 1, ), ); } + + async validate( + args: CreateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItem); + args.data.forEach((record) => { + if (record?.id) { + assertIsValidUuid(record.id); + } + }); + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 53dad3eddd0b..3540dcbf9559 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -1,33 +1,68 @@ +import { Injectable } from '@nestjs/common'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryDestroyOneResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryDestroyOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async destroyOne( + async resolve( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataItem } = options; + const { authContext, objectMetadataMapItem, objectMetadataMap } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); - const record = await repository.findOne({ + const nonFormattedRecordBeforeDeletion = await repository.findOne({ where: { id: args.id }, + withDeleted: true, }); + if (!nonFormattedRecordBeforeDeletion) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const recordBeforeDeletion = formatResult( + [nonFormattedRecordBeforeDeletion], + objectMetadataMapItem, + objectMetadataMap, + )[0]; + await repository.delete(args.id); - return record as ObjectRecord; + return recordBeforeDeletion as ObjectRecord; + } + + async validate( + args: DestroyOneResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.id) { + throw new GraphqlQueryRunnerException( + 'Missing id', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts new file mode 100644 index 000000000000..00561933043d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -0,0 +1,214 @@ +import { Injectable } from '@nestjs/common'; + +import isEmpty from 'lodash.isempty'; +import { In } from 'typeorm'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + Record as IRecord, + OrderByDirection, + RecordFilter, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { settings } from 'src/engine/constants/settings'; +import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; +import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryFindDuplicatesResolverService + implements + ResolverService[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: FindDuplicatesResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise[]> { + const { authContext, objectMetadataMapItem, objectMetadataMap } = options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + const existingRecordsQueryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + const duplicateRecordsQueryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMap[objectMetadataMapItem.nameSingular].fields, + objectMetadataMap, + ); + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + let objectRecords: Partial[] = []; + + if (args.ids) { + const nonFormattedObjectRecords = (await existingRecordsQueryBuilder + .where({ id: In(args.ids) }) + .getMany()) as ObjectRecord[]; + + objectRecords = formatResult( + nonFormattedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + } else if (args.data && !isEmpty(args.data)) { + objectRecords = formatData(args.data, objectMetadataMapItem); + } + + const duplicateConnections: IConnection[] = await Promise.all( + objectRecords.map(async (record) => { + const duplicateConditions = this.buildDuplicateConditions( + objectMetadataMapItem, + [record], + record.id, + ); + + if (isEmpty(duplicateConditions)) { + return typeORMObjectRecordsParser.createConnection( + [], + objectMetadataMapItem.nameSingular, + 0, + 0, + [{ id: OrderByDirection.AscNullsFirst }], + false, + false, + ); + } + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + duplicateRecordsQueryBuilder, + objectMetadataMapItem.nameSingular, + duplicateConditions, + ); + + const nonFormattedDuplicates = + (await withFilterQueryBuilder.getMany()) as ObjectRecord[]; + + const duplicates = formatResult( + nonFormattedDuplicates, + objectMetadataMapItem, + objectMetadataMap, + ); + + return typeORMObjectRecordsParser.createConnection( + duplicates, + objectMetadataMapItem.nameSingular, + duplicates.length, + duplicates.length, + [{ id: OrderByDirection.AscNullsFirst }], + false, + false, + ); + }), + ); + + return duplicateConnections; + } + + private buildDuplicateConditions( + objectMetadataMapItem: ObjectMetadataMapItem, + records?: Partial[] | undefined, + filteringByExistingRecordId?: string, + ): Partial { + if (!records || records.length === 0) { + return {}; + } + + const criteriaCollection = this.getApplicableDuplicateCriteriaCollection( + objectMetadataMapItem, + ); + + const conditions = records.flatMap((record) => { + const criteriaWithMatchingArgs = criteriaCollection.filter((criteria) => + criteria.columnNames.every((columnName) => { + const value = record[columnName] as string | undefined; + + return ( + value && value.length >= settings.minLengthOfStringForDuplicateCheck + ); + }), + ); + + return criteriaWithMatchingArgs.map((criteria) => { + const condition = {}; + + criteria.columnNames.forEach((columnName) => { + condition[columnName] = { eq: record[columnName] }; + }); + + return condition; + }); + }); + + const filter: Partial = {}; + + if (conditions && !isEmpty(conditions)) { + filter.or = conditions; + + if (filteringByExistingRecordId) { + filter.id = { neq: filteringByExistingRecordId }; + } + } + + return filter; + } + + private getApplicableDuplicateCriteriaCollection( + objectMetadataMapItem: ObjectMetadataMapItem, + ) { + return DUPLICATE_CRITERIA_COLLECTION.filter( + (duplicateCriteria) => + duplicateCriteria.objectName === objectMetadataMapItem.nameSingular, + ); + } + + async validate( + args: FindDuplicatesResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.data && !args.ids) { + throw new GraphqlQueryRunnerException( + 'You have to provide either "data" or "ids" argument', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + if (args.data && args.ids) { + throw new GraphqlQueryRunnerException( + 'You cannot provide both "data" and "ids" arguments', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + if (!args.ids && isEmpty(args.data)) { + throw new GraphqlQueryRunnerException( + 'The "data" condition can not be empty when "ids" input not provided', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index 5caa30e4e51e..85fdd3948274 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -1,6 +1,9 @@ +import { Injectable } from '@nestjs/common'; + import { isDefined } from 'class-validator'; import graphqlFields from 'graphql-fields'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, OrderByDirection, @@ -17,26 +20,25 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter'; -import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; import { - ObjectMetadataMapItem, - generateObjectMetadataMap, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; + getCursor, + getPaginationInfo, +} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryFindManyResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryFindManyResolverService + implements ResolverService> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async findMany< + async resolve< ObjectRecord extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, OrderBy extends RecordOrderBy = RecordOrderBy, @@ -44,51 +46,41 @@ export class GraphqlQueryFindManyResolverService { args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = + const { authContext, objectMetadataMapItem, info, objectMetadataMap } = options; - this.validateArgsOrThrow(args); - const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); const repository = dataSource.getRepository( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const countQueryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, - ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, + objectMetadataMapItem.nameSingular, ); - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, - ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder( countQueryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, args.filter ?? ({} as Filter), ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + objectMetadataMapItem, selectedFields, ); const isForwardPagination = !isDefined(args.before); @@ -105,7 +97,7 @@ export class GraphqlQueryFindManyResolverService { ? await withDeletedCountQueryBuilder.getCount() : 0; - const cursor = this.getCursor(args); + const cursor = getCursor(args); let appliedFilters = args.filter ?? ({} as Filter); @@ -118,7 +110,7 @@ export class GraphqlQueryFindManyResolverService { const cursorArgFilter = computeCursorArgFilter( cursor, orderByWithIdCondition, - objectMetadata.fields, + objectMetadataMapItem.fields, isForwardPagination, ); @@ -131,14 +123,14 @@ export class GraphqlQueryFindManyResolverService { const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, appliedFilters, ); const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder( withFilterQueryBuilder, orderByWithIdCondition, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, isForwardPagination, ); @@ -153,11 +145,11 @@ export class GraphqlQueryFindManyResolverService { const objectRecords = formatResult( nonFormattedObjectRecords, - objectMetadata, + objectMetadataMapItem, objectMetadataMap, ); - const { hasNextPage, hasPreviousPage } = this.getPaginationInfo( + const { hasNextPage, hasPreviousPage } = getPaginationInfo( objectRecords, limit, isForwardPagination, @@ -167,14 +159,12 @@ export class GraphqlQueryFindManyResolverService { objectRecords.pop(); } - const processNestedRelationsHelper = new ProcessNestedRelationsHelper( - this.twentyORMGlobalManager, - ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( objectMetadataMap, - objectMetadata, + objectMetadataMapItem, objectRecords, relations, limit, @@ -184,20 +174,25 @@ export class GraphqlQueryFindManyResolverService { } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - return typeORMObjectRecordsParser.createConnection( + const result = typeORMObjectRecordsParser.createConnection( objectRecords, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, limit, totalCount, orderByWithIdCondition, hasNextPage, hasPreviousPage, ); + + return result; } - private validateArgsOrThrow(args: FindManyResolverArgs) { + async validate( + args: FindManyResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { if (args.first && args.last) { throw new GraphqlQueryRunnerException( 'Cannot provide both first and last', @@ -235,49 +230,4 @@ export class GraphqlQueryFindManyResolverService { ); } } - - private getCursor( - args: FindManyResolverArgs, - ): Record | undefined { - if (args.after) return decodeCursor(args.after); - if (args.before) return decodeCursor(args.before); - - return undefined; - } - - private addOrderByColumnsToSelect( - order: Record, - select: Record, - ) { - for (const column of Object.keys(order || {})) { - if (!select[column]) { - select[column] = true; - } - } - } - - private addForeingKeyColumnsToSelect( - relations: Record, - select: Record, - objectMetadata: ObjectMetadataMapItem, - ) { - for (const column of Object.keys(relations || {})) { - if (!select[`${column}Id`] && objectMetadata.fields[`${column}Id`]) { - select[`${column}Id`] = true; - } - } - } - - private getPaginationInfo( - objectRecords: any[], - limit: number, - isForwardPagination: boolean, - ) { - const hasMoreRecords = objectRecords.length > limit; - - return { - hasNextPage: isForwardPagination && hasMoreRecords, - hasPreviousPage: !isForwardPagination && hasMoreRecords, - }; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index b9e86e420cc9..164f30b68664 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -1,5 +1,8 @@ +import { Injectable } from '@nestjs/common'; + import graphqlFields from 'graphql-fields'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, RecordFilter, @@ -13,28 +16,31 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { + WorkspaceQueryRunnerException, + WorkspaceQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryFindOneResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryFindOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async findOne< + async resolve< ObjectRecord extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, >( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = + ): Promise { + const { authContext, objectMetadataMapItem, info, objectMetadataMap } = options; const dataSource = @@ -43,37 +49,28 @@ export class GraphqlQueryFindOneResolverService { ); const repository = dataSource.getRepository( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, - ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + objectMetadataMapItem, selectedFields, ); const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, args.filter ?? ({} as Filter), ); @@ -86,12 +83,10 @@ export class GraphqlQueryFindOneResolverService { const objectRecord = formatResult( nonFormattedObjectRecord, - objectMetadata, + objectMetadataMapItem, objectMetadataMap, ); - const limit = QUERY_MAX_RECORDS; - if (!objectRecord) { throw new GraphqlQueryRunnerException( 'Record not found', @@ -99,32 +94,42 @@ export class GraphqlQueryFindOneResolverService { ); } - const processNestedRelationsHelper = new ProcessNestedRelationsHelper( - this.twentyORMGlobalManager, - ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const objectRecords = [objectRecord]; if (relations) { await processNestedRelationsHelper.processNestedRelations( objectMetadataMap, - objectMetadata, + objectMetadataMapItem, objectRecords, relations, - limit, + QUERY_MAX_RECORDS, authContext, dataSource, ); } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return typeORMObjectRecordsParser.processRecord( objectRecords[0], - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, 1, 1, ) as ObjectRecord; } + + async validate( + args: FindOneResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.filter || Object.keys(args.filter).length === 0) { + throw new WorkspaceQueryRunnerException( + 'Missing filter argument', + WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index c8d1892bbd2b..fe9d86f6cd84 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -1,3 +1,6 @@ +import { Injectable } from '@nestjs/common'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, OrderByDirection, @@ -11,47 +14,25 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -export class GraphqlQuerySearchResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - private featureFlagService: FeatureFlagService; - +@Injectable() +export class GraphqlQuerySearchResolverService + implements ResolverService> +{ constructor( - twentyORMGlobalManager: TwentyORMGlobalManager, - featureFlagService: FeatureFlagService, - ) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - this.featureFlagService = featureFlagService; - } + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly featureFlagService: FeatureFlagService, + ) {} - async search( + async resolve( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, objectMetadataCollection } = - options; - - const featureFlagsForWorkspace = - await this.featureFlagService.getWorkspaceFeatureFlags( - authContext.workspace.id, - ); - - const isQueryRunnerTwentyORMEnabled = - featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED; - - const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED; - - if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) { - throw new GraphqlQueryRunnerException( - 'This endpoint is not available yet, please use findMany instead.', - GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } + const { authContext, objectMetadataItem, objectMetadataMap } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( @@ -59,21 +40,8 @@ export class GraphqlQuerySearchResolverService { objectMetadataItem.nameSingular, ); - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - - const objectMetadata = objectMetadataMap[objectMetadataItem.nameSingular]; - - if (!objectMetadata) { - throw new GraphqlQueryRunnerException( - `Object metadata not found for ${objectMetadataItem.nameSingular}`, - GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); if (!args.searchInput) { return typeORMObjectRecordsParser.createConnection( @@ -100,7 +68,7 @@ export class GraphqlQuerySearchResolverService { 'DESC', ) .setParameter('searchTerms', searchTerms) - .limit(limit) + .take(limit) .getMany()) as ObjectRecord[]; const objectRecords = await repository.formatResult(resultsWithTsVector); @@ -129,4 +97,26 @@ export class GraphqlQuerySearchResolverService { return formattedWords.join(' | '); } + + async validate( + _args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const featureFlagsForWorkspace = + await this.featureFlagService.getWorkspaceFeatureFlags( + options.authContext.workspace.id, + ); + + const isQueryRunnerTwentyORMEnabled = + featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED; + + const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED; + + if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) { + throw new GraphqlQueryRunnerException( + 'This endpoint is not available yet, please use findMany instead.', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts new file mode 100644 index 000000000000..149674209329 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -0,0 +1,116 @@ +import { Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryUpdateManyResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + objectMetadataMapItem.nameSingular, + args.filter, + ); + + const data = formatData(args.data, objectMetadataMapItem); + + const result = await withFilterQueryBuilder + .update() + .set(data) + .returning('*') + .execute(); + + const nonFormattedUpdatedObjectRecords = result.raw; + + const updatedRecords = formatResult( + nonFormattedUpdatedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + updatedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return updatedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord( + record, + objectMetadataMapItem.nameSingular, + 1, + 1, + ), + ); + } + + async validate( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + args.filter?.id?.in?.forEach((id: string) => assertIsValidUuid(id)); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts new file mode 100644 index 000000000000..7be966cc656c --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -0,0 +1,123 @@ +import { Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryUpdateOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const withFilterQueryBuilder = queryBuilder.where({ id: args.id }); + + const data = formatData(args.data, objectMetadataMapItem); + + const result = await withFilterQueryBuilder + .update() + .set(data) + .returning('*') + .execute(); + + const nonFormattedUpdatedObjectRecords = result.raw; + + const updatedRecords = formatResult( + nonFormattedUpdatedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + if (updatedRecords.length === 0) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const updatedRecord = updatedRecords[0] as ObjectRecord; + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + [updatedRecord], + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord( + updatedRecord, + objectMetadataMapItem.nameSingular, + 1, + 1, + ); + } + + async validate( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + assertIsValidUuid(args.id); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts new file mode 100644 index 000000000000..8cb2c9cc7a04 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts @@ -0,0 +1,137 @@ +import { Injectable } from '@nestjs/common'; + +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; + +@Injectable() +export class ApiEventEmitterService { + constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {} + + public emitCreateEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.created`, + records.map((record) => ({ + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: null, + after: this.removeGraphQLAndNestedProperties(record), + }, + })), + authContext.workspace.id, + ); + } + + public emitUpdateEvents( + existingRecords: T[], + records: T[], + updatedFields: string[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + const mappedExistingRecords = existingRecords.reduce( + (acc, { id, ...record }) => ({ + ...acc, + [id]: record, + }), + {}, + ); + + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.updated`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: mappedExistingRecords[record.id] + ? this.removeGraphQLAndNestedProperties( + mappedExistingRecords[record.id], + ) + : undefined, + after: this.removeGraphQLAndNestedProperties(record), + updatedFields, + }, + }; + }), + authContext.workspace.id, + ); + } + + public emitDeletedEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.deleted`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: this.removeGraphQLAndNestedProperties(record), + after: null, + }, + }; + }), + authContext.workspace.id, + ); + } + + public emitDestroyEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.destroyed`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: this.removeGraphQLAndNestedProperties(record), + after: null, + }, + }; + }), + authContext.workspace.id, + ); + } + + private removeGraphQLAndNestedProperties( + record: ObjectRecord, + ) { + if (!record) { + return {}; + } + + const sanitizedRecord = {}; + + for (const [key, value] of Object.entries(record)) { + if (value && typeof value === 'object' && value['edges']) { + continue; + } + + if (key === '__typename') { + continue; + } + + sanitizedRecord[key] = value; + } + + return sanitizedRecord; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts index bf8eb52d0a57..bd27522ce1b2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts @@ -2,6 +2,7 @@ import { Record as IRecord, RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { GraphqlQueryRunnerException, @@ -44,3 +45,25 @@ export const encodeCursor = ( return Buffer.from(JSON.stringify(cursorData)).toString('base64'); }; + +export const getCursor = ( + args: FindManyResolverArgs, +): Record | undefined => { + if (args.after) return decodeCursor(args.after); + if (args.before) return decodeCursor(args.before); + + return undefined; +}; + +export const getPaginationInfo = ( + objectRecords: any[], + limit: number, + isForwardPagination: boolean, +) => { + const hasMoreRecords = objectRecords.length > limit; + + return { + hasNextPage: isForwardPagination && hasMoreRecords, + hasPreviousPage: !isForwardPagination && hasMoreRecords, + }; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts index 45883f99ddf3..d960c3d45a7f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts @@ -4,6 +4,10 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + ObjectMetadataMap, + ObjectMetadataMapItem, +} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export interface WorkspaceQueryRunnerOptions { authContext: AuthContext; @@ -11,4 +15,6 @@ export interface WorkspaceQueryRunnerOptions { objectMetadataItem: ObjectMetadataInterface; fieldMetadataCollection: FieldMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[]; + objectMetadataMap: ObjectMetadataMap; + objectMetadataMapItem: ObjectMetadataMapItem; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts index b75c939d1ac7..034c73bda552 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts @@ -9,6 +9,7 @@ import { FindManyResolverArgs, FindOneResolverArgs, RestoreManyResolverArgs, + SearchResolverArgs, UpdateManyResolverArgs, UpdateOneResolverArgs, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -42,4 +43,6 @@ export type WorkspacePreQueryHookPayload = T extends 'createMany' ? DestroyManyResolverArgs : T extends 'destroyOne' ? DestroyOneResolverArgs - : never; + : T extends 'search' + ? SearchResolverArgs + : never; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index 872ab7906fca..06a8d5507b2d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { CreateManyResolverArgs, @@ -32,12 +33,14 @@ export class CreateManyResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; const isQueryRunnerTwentyORMEnabled = diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index edf9206a1cde..5922d05550d6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { CreateOneResolverArgs, @@ -32,12 +33,14 @@ export class CreateOneResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; const isQueryRunnerTwentyORMEnabled = @@ -50,13 +53,7 @@ export class CreateOneResolverFactory return await this.graphqlQueryRunnerService.createOne(args, options); } - return await this.workspaceQueryRunnerService.createOne(args, { - authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, - info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - }); + return await this.workspaceQueryRunnerService.createOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index a8d36f3e4900..4a32ad5ea1c5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DeleteManyResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class DeleteManyResolverFactory @@ -18,6 +22,8 @@ export class DeleteManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class DeleteManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.deleteMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.deleteMany(args, options); + } + + return await this.workspaceQueryRunnerService.deleteMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index 93f249cdd6ac..d58ebe02fd56 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DeleteOneResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class DeleteOneResolverFactory @@ -18,6 +22,8 @@ export class DeleteOneResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class DeleteOneResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.deleteOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.deleteOne(args, options); + } + + return await this.workspaceQueryRunnerService.deleteOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index 4a064a406b9b..2e6cf835effa 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DestroyManyResolverArgs, @@ -27,13 +28,20 @@ export class DestroyManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.destroyMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.workspaceQueryRunnerService.destroyMany( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index 4c204d6e8c0c..bb1e2aaaa9ba 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DestroyOneResolverArgs, @@ -27,13 +28,17 @@ export class DestroyOneResolverFactory return async (_source, args, context, info) => { try { - return await this.graphQLQueryRunnerService.destroyOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.graphQLQueryRunnerService.destroyOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 0a1494efb666..f8b57ad22cca 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindDuplicatesResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class FindDuplicatesResolverFactory @@ -18,6 +22,8 @@ export class FindDuplicatesResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,33 @@ export class FindDuplicatesResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.findDuplicates(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.findDuplicates( + args, + options, + ); + } + + return await this.workspaceQueryRunnerService.findDuplicates( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index 2dd452a2976c..c695079e2f62 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindManyResolverArgs, @@ -27,12 +28,14 @@ export class FindManyResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; return await this.graphqlQueryRunnerService.findMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index 3dbbc2330d06..00845e841710 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindOneResolverArgs, @@ -27,12 +28,14 @@ export class FindOneResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; return await this.graphqlQueryRunnerService.findOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index ceba95306aed..d92210040535 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class RestoreManyResolverFactory @@ -18,6 +22,8 @@ export class RestoreManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,33 @@ export class RestoreManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.restoreMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.restoreMany( + args, + options, + ); + } + + return await this.workspaceQueryRunnerService.restoreMany( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts index 5b32d527960f..9d559b656194 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -25,13 +26,17 @@ export class SearchResolverFactory return async (_source, args, _context, info) => { try { - return await this.graphqlQueryRunnerService.search(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.graphqlQueryRunnerService.search(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index c2328d12ba05..11027e4cc4fd 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class UpdateManyResolverFactory @@ -18,6 +22,8 @@ export class UpdateManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class UpdateManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.updateMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.updateMany(args, options); + } + + return await this.workspaceQueryRunnerService.updateMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index c7a7dc6bacd6..13a2e4f714d1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class UpdateOneResolverFactory @@ -18,6 +22,8 @@ export class UpdateOneResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class UpdateOneResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.updateOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.updateOne(args, options); + } + + return await this.workspaceQueryRunnerService.updateOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index 616c734581df..a652e3065c81 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -11,6 +11,7 @@ import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-res import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { getResolverName } from 'src/engine/utils/get-resolver-name.util'; import { CreateManyResolverFactory } from './factories/create-many-resolver.factory'; @@ -49,6 +50,7 @@ export class WorkspaceResolverFactory { async create( authContext: AuthContext, objectMetadataCollection: ObjectMetadataInterface[], + objectMetadataMap: ObjectMetadataMap, workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods, ): Promise { const factories = new Map< @@ -94,7 +96,9 @@ export class WorkspaceResolverFactory { authContext, objectMetadataItem: objectMetadata, fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection: objectMetadataCollection, + objectMetadataCollection, + objectMetadataMap, + objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], }); } @@ -117,7 +121,9 @@ export class WorkspaceResolverFactory { authContext, objectMetadataItem: objectMetadata, fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection: objectMetadataCollection, + objectMetadataCollection, + objectMetadataMap, + objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts index f5a6aec8b1a2..d0ab66983309 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts @@ -2,10 +2,16 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + ObjectMetadataMap, + ObjectMetadataMapItem, +} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export interface WorkspaceSchemaBuilderContext { authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; fieldMetadataCollection: FieldMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[]; + objectMetadataItem: ObjectMetadataInterface; + objectMetadataMap: ObjectMetadataMap; + objectMetadataMapItem: ObjectMetadataMapItem; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 336fa825f81c..32a44ad4d26e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -117,6 +117,7 @@ export class WorkspaceSchemaFactory { const autoGeneratedResolvers = await this.workspaceResolverFactory.create( authContext, objectMetadataCollection, + objectMetadataMap, workspaceResolverBuilderMethodNames, ); const scalarsResolvers = diff --git a/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts b/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts index d7ed6f87bd85..48d021b5e689 100644 --- a/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts +++ b/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { Record as IRecord, Record, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { settings } from 'src/engine/constants/settings'; +import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; @Injectable() export class DuplicateService { @@ -94,80 +94,4 @@ export class DuplicateService { duplicateCriteria.objectName === objectMetadataItem.nameSingular, ); } - - /** - * TODO: Remove this code by September 1st, 2024 if it isn't used - * It was build to be used by the upsertMany function, but it was not used. - * It's a re-implementation of the methods to findDuplicates, but done - * at the SQL layer instead of doing it at the GraphQL layer - * - async findDuplicate( - data: Partial, - objectMetadata: ObjectMetadataInterface, - workspaceId: string, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const { duplicateWhereClause, duplicateWhereParameters } = - this.buildDuplicateConditionForUpsert(objectMetadata, data); - - const results = await this.workspaceDataSourceService.executeRawQuery( - ` - SELECT - * - FROM - ${dataSourceSchema}."${computeObjectTargetTable( - objectMetadata, - )}" p - WHERE - ${duplicateWhereClause} - `, - duplicateWhereParameters, - workspaceId, - ); - - return results.length > 0 ? results[0] : null; - } - - private buildDuplicateConditionForUpsert( - objectMetadata: ObjectMetadataInterface, - data: Partial, - ) { - const criteriaCollection = this.getApplicableDuplicateCriteriaCollection( - objectMetadata, - ).filter( - (duplicateCriteria) => duplicateCriteria.useAsUniqueKeyForUpsert === true, - ); - - const whereClauses: string[] = []; - const whereParameters: any[] = []; - let parameterIndex = 1; - - criteriaCollection.forEach((c) => { - const clauseParts: string[] = []; - - c.columnNames.forEach((column) => { - const dataKey = Object.keys(data).find( - (key) => key.toLowerCase() === column.toLowerCase(), - ); - - if (dataKey) { - clauseParts.push(`p."${column}" = $${parameterIndex}`); - whereParameters.push(data[dataKey]); - parameterIndex++; - } - }); - if (clauseParts.length > 0) { - whereClauses.push(`(${clauseParts.join(' AND ')})`); - } - }); - - const duplicateWhereClause = whereClauses.join(' OR '); - const duplicateWhereParameters = whereParameters; - - return { duplicateWhereClause, duplicateWhereParameters }; - } - * - */ } diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts index 7e44586d00eb..07471ac9b2da 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts @@ -60,8 +60,8 @@ export class IndexMetadataEntity { @Column({ type: 'enum', enum: IndexType, - nullable: true, default: IndexType.BTREE, + nullable: false, }) indexType?: IndexType; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts index f6f31f32c9db..cd3851393678 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts @@ -1,9 +1,11 @@ -import { isPlainObject } from '@nestjs/common/utils/shared.utils'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; -import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection'; +import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; +import { capitalize } from 'src/utils/capitalize'; export function formatData( data: T, @@ -17,49 +19,70 @@ export function formatData( return data.map((item) => formatData(item, objectMetadata)) as T; } - const compositeFieldMetadataCollection = - getCompositeFieldMetadataCollection(objectMetadata); - - const compositeFieldMetadataMap = new Map( - compositeFieldMetadataCollection.map((fieldMetadata) => [ - fieldMetadata.name, - fieldMetadata, - ]), - ); - const newData: object = {}; + const newData: Record = {}; for (const [key, value] of Object.entries(data)) { - const fieldMetadata = compositeFieldMetadataMap.get(key); + const fieldMetadata = objectMetadata.fields[key]; if (!fieldMetadata) { - if (isPlainObject(value)) { - newData[key] = formatData(value, objectMetadata); - } else { - newData[key] = value; - } - continue; + throw new Error( + `Field metadata for field "${key}" is missing in object metadata`, + ); } - const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); + if (isCompositeFieldMetadataType(fieldMetadata.type)) { + const formattedCompositeField = formatCompositeField( + value, + fieldMetadata, + ); - if (!compositeType) { - continue; + Object.assign(newData, formattedCompositeField); + } else { + newData[key] = formatFieldMetadataValue(value, fieldMetadata); } + } - for (const compositeProperty of compositeType.properties) { - const compositeKey = computeCompositeColumnName( - fieldMetadata.name, - compositeProperty, - ); - const value = data?.[key]?.[compositeProperty.name]; + return newData as T; +} - if (value === undefined || value === null) { - continue; - } +function formatCompositeField( + value: any, + fieldMetadata: FieldMetadataInterface, +): Record { + const compositeType = compositeTypeDefinitions.get( + fieldMetadata.type as CompositeFieldMetadataType, + ); - newData[compositeKey] = data[key][compositeProperty.name]; + if (!compositeType) { + throw new Error( + `Composite type definition not found for type: ${fieldMetadata.type}`, + ); + } + + const formattedCompositeField: Record = {}; + + for (const property of compositeType.properties) { + const subFieldKey = property.name; + const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`; + + if (value && value[subFieldKey] !== undefined) { + formattedCompositeField[fullFieldName] = formatFieldMetadataValue( + value[subFieldKey], + property as unknown as FieldMetadataInterface, + ); } } - return newData as T; + return formattedCompositeField; +} + +function formatFieldMetadataValue( + value: any, + fieldMetadata: FieldMetadataInterface, +) { + if (fieldMetadata.type === FieldMetadataType.RAW_JSON) { + return JSON.parse(value as string); + } + + return value; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts index 81949e0743a3..2b29ea908cd8 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts @@ -1,6 +1,9 @@ import { isPlainObject } from '@nestjs/common/utils/shared.utils'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { @@ -81,9 +84,15 @@ export function formatResult( if (!compositePropertyArgs && !relationMetadata) { if (isPlainObject(value)) { newData[key] = formatResult(value, objectMetadata, objectMetadataMap); + } else if (objectMetadata.fields[key]) { + newData[key] = formatFieldMetadataValue( + value, + objectMetadata.fields[key], + ); } else { newData[key] = value; } + continue; } @@ -129,3 +138,18 @@ export function formatResult( return newData as T; } + +function formatFieldMetadataValue( + value: any, + fieldMetadata: FieldMetadataInterface, +) { + if ( + typeof value === 'string' && + (fieldMetadata.type === FieldMetadataType.MULTI_SELECT || + fieldMetadata.type === FieldMetadataType.ARRAY) + ) { + return value.replace(/{|}/g, '').split(','); + } + + return value; +} From 424c4890b00b0bab59b6b7754b4e191bfdce8217 Mon Sep 17 00:00:00 2001 From: Harshit Singh <73997189+harshit078@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:08:15 +0530 Subject: [PATCH 06/16] fix: New Relation Design hot fix (#7423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description - This PR solves the issue #7353 - [x] Improved layout for mobile and desktop - [ ] Added tooltip on hover --------- Co-authored-by: Nitin Koche Co-authored-by: Félix Malfait --- .../SettingsDataModelFieldRelationForm.tsx | 10 ++++---- ...DataModelFieldRelationSettingsFormCard.tsx | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx index 83f81326436a..d6f3fe48be10 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx @@ -14,6 +14,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { RelationDefinitionType } from '~/generated-metadata/graphql'; export const settingsDataModelFieldRelationFormSchema = z.object({ @@ -44,13 +45,12 @@ const StyledContainer = styled.div` padding: ${({ theme }) => theme.spacing(4)}; `; -const StyledSelectsContainer = styled.div` +const StyledSelectsContainer = styled.div<{ isMobile: boolean }>` display: grid; gap: ${({ theme }) => theme.spacing(4)}; - grid-template-columns: 1fr 1fr; + grid-template-columns: ${({ isMobile }) => (isMobile ? '1fr' : '1fr 1fr')}; margin-bottom: ${({ theme }) => theme.spacing(4)}; `; - const StyledInputsLabel = styled.span` color: ${({ theme }) => theme.font.color.light}; display: block; @@ -98,9 +98,11 @@ export const SettingsDataModelFieldRelationForm = ({ watchFormValue('relation.objectMetadataId'), ); + const isMobile = useIsMobile(); + return ( - + & Partial>; @@ -27,14 +27,23 @@ const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` flex: 1 1 100%; `; -const StyledPreviewContent = styled.div` +const StyledPreviewContent = styled.div<{ isMobile: boolean }>` display: flex; - flex-direction: column; gap: 6px; + flex-direction: ${({ isMobile }) => (isMobile ? 'column' : 'row')}; `; -const StyledRelationImage = styled.img<{ flip?: boolean }>` - transform: ${({ flip }) => (flip ? 'scaleX(-1) rotate(270deg)' : 'none')}; +const StyledRelationImage = styled.img<{ flip?: boolean; isMobile: boolean }>` + transform: ${({ flip, isMobile }) => { + let transform = ''; + if (isMobile) { + transform += 'rotate(90deg) '; + } + if (flip === true) { + transform += 'scaleX(-1)'; + } + return transform.trim(); + }}; margin: auto; width: 54px; `; @@ -46,7 +55,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ const { watch: watchFormValue } = useFormContext(); const { findObjectMetadataItemById } = useFilteredObjectMetadataItems(); - + const isMobile = useIsMobile(); const { initialRelationObjectMetadataItem, initialRelationType, @@ -69,7 +78,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ return ( + Date: Fri, 4 Oct 2024 20:47:05 +0530 Subject: [PATCH 07/16] oss.gg:quest points tracking files/folders (#7430) ### create all oss.gg quest tracking file which oss.gg hackathon contributor come and there proof of work ### let me know if any changes required :slightly_smiling_face: also can I get some brownie oss.gg point ? for this PR ### thank you ![image](https://github.com/user-attachments/assets/d029c696-4c01-441f-abde-51ef09c24a93) ![image](https://github.com/user-attachments/assets/71f306f2-8e4c-4f14-83dd-84ecaaf3082f) ![image](https://github.com/user-attachments/assets/eef363ef-040f-4427-a998-9995dd33bc80) ![image](https://github.com/user-attachments/assets/e404205a-0387-4af0-8580-0619baf2e4cc) ![image](https://github.com/user-attachments/assets/86206a0b-7d1a-4f45-ad3d-aa692cbad71f) --- .../1-create-youtube-video-about-20.md | 21 +++++++++++++++++ .../2-write-blog-post-about-20.md | 21 +++++++++++++++++ .../3-write-selfthost-guide-blog-post-20.md | 21 +++++++++++++++++ .../4-create-promotional-video-20-share.md | 21 +++++++++++++++++ .../1-design-promotional-poster-20-share.md | 21 +++++++++++++++++ .../2-design-new-logo-twenty.md | 21 +++++++++++++++++ .../3-create-custom-interfact-theme-20.md | 21 +++++++++++++++++ ...-write-migration-script-other-crm-to-20.md | 21 +++++++++++++++++ .../2-create-raycast-integration-for-20.md | 21 +++++++++++++++++ .../1-create-n8n-template-integrate-20-API.md | 21 +++++++++++++++++ .../2-write-selfthost-guide-blog-post-20.md | 21 +++++++++++++++++ .../1-quote-tweet-20-oss-gg-launch.md | 23 +++++++++++++++++++ .../2-tweet-about-fav-twenty-feature.md | 23 +++++++++++++++++++ oss-gg/twenty-side-quest/3-meme-magic.md | 23 +++++++++++++++++++ oss-gg/twenty-side-quest/4-gif-magic.md | 23 +++++++++++++++++++ oss-gg/twenty-side-quest/5-quest-wizard.md | 19 +++++++++++++++ 16 files changed, 342 insertions(+) create mode 100644 oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md create mode 100644 oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md create mode 100644 oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md create mode 100644 oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md create mode 100644 oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md create mode 100644 oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md create mode 100644 oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md create mode 100644 oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md create mode 100644 oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md create mode 100644 oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md create mode 100644 oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md create mode 100644 oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md create mode 100644 oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md create mode 100644 oss-gg/twenty-side-quest/3-meme-magic.md create mode 100644 oss-gg/twenty-side-quest/4-gif-magic.md create mode 100644 oss-gg/twenty-side-quest/5-quest-wizard.md diff --git a/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md b/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md new file mode 100644 index 000000000000..455b5e35bae3 --- /dev/null +++ b/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Create a YouTube Video about Twenty showcasing a specific way to use Twenty effectively. +**Points**: 750 Points +**Proof**: Add your oss handle and YouTube video link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » YouTube Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) YouTube Link: [YouTube](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md b/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md new file mode 100644 index 000000000000..a4c4e6bee944 --- /dev/null +++ b/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a blog post about sharing your experience using Twenty in a detailed format on any platform. +**Points**: 750 Points +**Proof**: Add your oss handle and blog link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md b/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md new file mode 100644 index 000000000000..c7352ec430fc --- /dev/null +++ b/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a blog post about self-hosting Twenty in a detailed format on any platform. +**Points**: 750 Points +**Proof**: Add your oss handle and blog link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md b/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md new file mode 100644 index 000000000000..e52cb43a4247 --- /dev/null +++ b/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md @@ -0,0 +1,21 @@ +**Side Quest**: Create a promotional video for Twenty and share it on social media. +**Points**: 750 Points +**Proof**: Add your oss handle and video link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md b/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md new file mode 100644 index 000000000000..b995788bc3f1 --- /dev/null +++ b/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md @@ -0,0 +1,21 @@ +**Side Quest**: Design a promotional poster of Twenty and share it on social media. +**Points**: 300 Points +**Proof**: Add your oss handle and poster link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » poster Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) poster Link: [poster](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md b/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md new file mode 100644 index 000000000000..e8e70ca913cb --- /dev/null +++ b/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md @@ -0,0 +1,21 @@ +**Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm. +**Points**: 300 Points +**Proof**: Create a logo uploade it on any of the platform and add your oss handle and logo link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » Logo Link: https://link.to/content » tweet Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Logo Link: [logo](https://twenty.com/) tweet Link: [tweet](https://x.com) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md b/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md new file mode 100644 index 000000000000..e51945ea9988 --- /dev/null +++ b/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Duplicate the Figma file from the main repo and customize the variables to create a unique interface theme for Twenty. +**Points**: 750 Points +**Proof**: Add your oss handle and Figma link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » Figma Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md b/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md new file mode 100644 index 000000000000..249d8e158cfa --- /dev/null +++ b/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty. +**Points**: 750 Points +**Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md b/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md new file mode 100644 index 000000000000..e4793c40d66f --- /dev/null +++ b/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Develop an integration for Raycast that enables users to create records on any object within Twenty directly from Raycast. +**Points**: 1500 Points +**Proof**: Add your oss handle and record video and share link to the list below. In video show the workflow of the your integration created and perform some task. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md b/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md new file mode 100644 index 000000000000..6786e5a94553 --- /dev/null +++ b/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md @@ -0,0 +1,21 @@ +**Side Quest**: Create an n8n workflow that empowers Twenty by connecting it to another tool. +**Points**: 750 Points +**Proof**: Add your oss handle and template link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » template Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md b/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md new file mode 100644 index 000000000000..58fa6de4d8d6 --- /dev/null +++ b/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a comprehensive guide on how to integrate Twenty with marketing automation tool (n8n, Zapier). Include a concrete use case and explain how to leverage AI to write API requests for non-developers and share it. +**Points**: 1500 Points +**Proof**: Add your oss handle and guide link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » guide Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md b/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md b/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/3-meme-magic.md b/oss-gg/twenty-side-quest/3-meme-magic.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/3-meme-magic.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/4-gif-magic.md b/oss-gg/twenty-side-quest/4-gif-magic.md new file mode 100644 index 000000000000..0e38ace584d6 --- /dev/null +++ b/oss-gg/twenty-side-quest/4-gif-magic.md @@ -0,0 +1,23 @@ +**Side Quest**: GIF Magic - Craft a GIF where a brick plays a role. Upload it to GIPHY with tags 'open source', 'foss', 'papermarkio'. +**Points**: 150 Points +**Proof**: Add a screenshot of GIF on Giphy to the PR description. Add a link to your GIPHY in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://giphy.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/5-quest-wizard.md b/oss-gg/twenty-side-quest/5-quest-wizard.md new file mode 100644 index 000000000000..2dfe4bd9c86b --- /dev/null +++ b/oss-gg/twenty-side-quest/5-quest-wizard.md @@ -0,0 +1,19 @@ +**Side Quest**: Complete all papermarkio side quests +**Points**: 300 Points +**Proof**: Add screenshots for each side quest to the PR description. Add your name to the list below. + +Please follow the following schema: + +--- + + » 05-April-2024 by YOUR NAME + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by X + +--- From 2f223f329461d9e7e6ba360a44b6876928674c59 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 4 Oct 2024 18:31:19 +0200 Subject: [PATCH 08/16] Fix 'name' column wrongly added in standard objects (#7428) ## Context Name shouldn't be added to all tables, especially standard objects because they already have their own labelIdentifierFieldMetadata specified in the workspace-entity schema. This PR removes this column from the "base" list of columns to add when creating a new object/table and moves it to the object-metadata service that is, as of today, only used for custom objects. Also had to modify the migration-runner to handle column creation in a table creation migration (this was available in the migration definition already but was not doing anything) This also fixes an issue in standard objects that already have a "name" field defined with a different field type, this is even more important when the said field is a composite field. For example people already has a FULL_NAME name field which clashes with the default TEXT name field meaning it was only creating 1 field metadata for 'name' but 3 columns were created: `name, nameFirstName, nameLastName`. This inconsistency with metadata (which is our source of truth everywhere) brought some issues (lately, converting back typeorm response to gql (including composition) was broken). --- ...phql-query-update-many-resolver.service.ts | 3 +- ...aphql-query-update-one-resolver.service.ts | 8 +-- .../object-metadata.service.ts | 39 ++++++++++++- ...ations-for-custom-object-relations.util.ts | 4 -- .../twenty-orm/base.workspace-entity.ts | 2 +- .../utils/custom-table-default-column.util.ts | 55 ------------------- .../utils/table-default-column.util.ts | 10 ++++ .../workspace-migration-runner.service.ts | 21 ++++++- .../calendar/common/types/calendar-event.ts | 2 + .../message-import-manager/types/message.ts | 2 + 10 files changed, 74 insertions(+), 72 deletions(-) delete mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 149674209329..d8854223794b 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -66,8 +66,7 @@ export class GraphqlQueryUpdateManyResolverService const data = formatData(args.data, objectMetadataMapItem); const result = await withFilterQueryBuilder - .update() - .set(data) + .update(data) .returning('*') .execute(); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index 7be966cc656c..6fc4e1a72c58 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -61,13 +61,11 @@ export class GraphqlQueryUpdateOneResolverService objectMetadataMapItem.nameSingular, ); - const withFilterQueryBuilder = queryBuilder.where({ id: args.id }); - const data = formatData(args.data, objectMetadataMapItem); - const result = await withFilterQueryBuilder - .update() - .set(data) + const result = await queryBuilder + .update(data) + .where({ id: args.id }) .returning('*') .execute(); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index b4102db85746..ff08237d20fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -49,8 +49,10 @@ import { generateMigrationName } from 'src/engine/metadata-modules/workspace-mig import { WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnDrop, + WorkspaceMigrationTableAction, WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -103,6 +105,7 @@ export class ObjectMetadataService extends TypeOrmQueryService [ - { - name: computeObjectTargetTable(createdObjectMetadata), - action: WorkspaceMigrationTableActionType.CREATE, - } satisfies WorkspaceMigrationTableAction, // Add activity target relation { name: computeObjectTargetTable(activityTargetObjectMetadata), diff --git a/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts b/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts index d9a19a689301..53355304c346 100644 --- a/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts +++ b/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts @@ -55,5 +55,5 @@ export abstract class BaseWorkspaceEntity { }, }) @WorkspaceIsNullable() - deletedAt?: string | null; + deletedAt: string | null; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts deleted file mode 100644 index 11a831f809d4..000000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { TableColumnOptions } from 'typeorm'; - -import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; - -export const customTableDefaultColumns = ( - tableName: string, -): TableColumnOptions[] => [ - { - name: 'id', - type: 'uuid', - isPrimary: true, - default: 'public.uuid_generate_v4()', - }, - { - name: 'createdAt', - type: 'timestamptz', - default: 'now()', - }, - { - name: 'updatedAt', - type: 'timestamptz', - default: 'now()', - }, - { - name: 'deletedAt', - type: 'timestamptz', - isNullable: true, - }, - { - name: 'position', - type: 'float', - isNullable: true, - }, - { - name: 'name', - type: 'text', - isNullable: false, - default: "'Untitled'", - }, - { - name: 'createdBySource', - type: 'enum', - enumName: `${tableName}_createdBySource_enum`, - enum: Object.values(FieldActorSource), - isNullable: false, - default: `'${FieldActorSource.MANUAL}'`, - }, - { name: 'createdByWorkspaceMemberId', type: 'uuid', isNullable: true }, - { - name: 'createdByName', - type: 'text', - isNullable: false, - default: "''", - }, -]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts new file mode 100644 index 000000000000..8967eb83f0d9 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts @@ -0,0 +1,10 @@ +import { TableColumnOptions } from 'typeorm'; + +export const tableDefaultColumns = (): TableColumnOptions[] => [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + default: 'public.uuid_generate_v4()', + }, +]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts index 2d6a20e147e1..db2072c8447b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts @@ -26,10 +26,10 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service'; import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util'; +import { tableDefaultColumns } from 'src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util'; import { isDefined } from 'src/utils/is-defined'; import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service'; -import { customTableDefaultColumns } from './utils/custom-table-default-column.util'; @Injectable() export class WorkspaceMigrationRunnerService { @@ -121,7 +121,12 @@ export class WorkspaceMigrationRunnerService { ) { switch (tableMigration.action) { case WorkspaceMigrationTableActionType.CREATE: - await this.createTable(queryRunner, schemaName, tableMigration.name); + await this.createTable( + queryRunner, + schemaName, + tableMigration.name, + tableMigration.columns, + ); break; case WorkspaceMigrationTableActionType.ALTER: { if (tableMigration.newName) { @@ -244,16 +249,26 @@ export class WorkspaceMigrationRunnerService { queryRunner: QueryRunner, schemaName: string, tableName: string, + columns?: WorkspaceMigrationColumnAction[], ) { await queryRunner.createTable( new Table({ name: tableName, schema: schemaName, - columns: customTableDefaultColumns(tableName), + columns: tableDefaultColumns(), }), true, ); + if (columns && columns.length > 0) { + await this.handleColumnChanges( + queryRunner, + schemaName, + tableName, + columns, + ); + } + // Enable totalCount for the table await queryRunner.query(` COMMENT ON TABLE "${schemaName}"."${tableName}" IS '@graphql({"totalCount": {"enabled": true}})'; diff --git a/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts b/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts index 00f4c82ac5c5..ae817cc3fae7 100644 --- a/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts +++ b/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts @@ -5,6 +5,7 @@ export type CalendarEvent = Omit< CalendarEventWorkspaceEntity, | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'calendarChannelEventAssociations' | 'calendarEventParticipants' | 'conferenceLink' @@ -19,6 +20,7 @@ export type CalendarEventParticipant = Omit< | 'id' | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'personId' | 'workspaceMemberId' | 'person' diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts index 4ec483869a8d..b665e54898a8 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts @@ -6,6 +6,7 @@ export type Message = Omit< MessageWorkspaceEntity, | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'messageChannelMessageAssociations' | 'messageParticipants' | 'messageThread' @@ -25,6 +26,7 @@ export type MessageParticipant = Omit< | 'id' | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'personId' | 'workspaceMemberId' | 'person' From ae2bd66f45f1079564c92085347c46e6ec992100 Mon Sep 17 00:00:00 2001 From: bmbaji <116686135+bmbaji@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:57:15 -0400 Subject: [PATCH 09/16] changed the createdByName to Twenty(Sample data). (#7424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I changed the createdByName from' system' to 'Twenty(Sample Data)'. --------- Co-authored-by: Félix Malfait --- .../utils/getSourceEnumOptions.ts | 8 ++++++++ .../ui/field/display/components/ActorDisplay.tsx | 6 +++++- .../composite-types/actor.composite-type.ts | 1 + .../standard-objects-prefill-data/company.ts | 12 +++++++----- .../standard-objects-prefill-data/person.ts | 11 ++++++----- .../src/display/icon/components/TablerIcons.ts | 7 ++++--- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts index ee886da633e0..7006bb1ed8f1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts @@ -4,6 +4,7 @@ import { IconCsv, IconGmail, IconGoogleCalendar, + IconRobot, IconSettingsAutomation, IconUserCircle, } from 'twenty-ui'; @@ -52,5 +53,12 @@ export const getSourceEnumOptions = ( AvatarIcon: IconSettingsAutomation, isIconInverted: true, }, + { + id: 'SYSTEM', + name: 'System', + isSelected: selectedItemIds.includes('SYSTEM'), + AvatarIcon: IconRobot, + isIconInverted: true, + }, ]; }; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx index ba107aa612f0..b5111d245a0a 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx @@ -7,6 +7,7 @@ import { IconCalendar, IconCsv, IconGmail, + IconRobot, } from 'twenty-ui'; type ActorDisplayProps = Partial & { @@ -29,12 +30,15 @@ export const ActorDisplay = ({ return IconGmail; case 'CALENDAR': return IconCalendar; + case 'SYSTEM': + return IconRobot; default: return undefined; } }, [source]); - const isIconInverted = source === 'API' || source === 'IMPORT'; + const isIconInverted = + source === 'API' || source === 'IMPORT' || source === 'SYSTEM'; return ( Date: Fri, 4 Oct 2024 19:08:08 +0200 Subject: [PATCH 10/16] Adding instruction for running redis (#7434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #7433 --------- Co-authored-by: Félix Malfait --- .../src/content/developers/local-setup.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/twenty-website/src/content/developers/local-setup.mdx b/packages/twenty-website/src/content/developers/local-setup.mdx index 7e436c1aa304..69cf9cb630e7 100644 --- a/packages/twenty-website/src/content/developers/local-setup.mdx +++ b/packages/twenty-website/src/content/developers/local-setup.mdx @@ -185,6 +185,24 @@ yarn ## Step 7: Running the project +Start your redis server: + + + Depending on your Linux distribution, Redis server might be started automatically. + If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro. + + + ```bash + brew services start redis + ``` + + + Depending on your Linux distribution, Redis server might be started automatically. + If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro. + + + + Setup your database with the following command: ```bash npx nx database:reset twenty-server From bd305c84320fce442b6237a02c5a4f8e84452fac Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 4 Oct 2024 19:28:29 +0200 Subject: [PATCH 11/16] Fix worker run ci step (#7437) ## Context Updating the Worker / Run step to run in sync mode with in-memory cache type so it does not hang forever in the CI. --- .github/workflows/ci-server.yaml | 2 +- packages/twenty-server/project.json | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-server.yaml b/.github/workflows/ci-server.yaml index 857dec2fc863..074d63fdda40 100644 --- a/.github/workflows/ci-server.yaml +++ b/.github/workflows/ci-server.yaml @@ -54,7 +54,7 @@ jobs: - name: Server / Write .env run: npx nx reset:env twenty-server - name: Worker / Run - run: MESSAGE_QUEUE_TYPE=sync npx nx worker twenty-server + run: npx nx run twenty-server:worker:ci server-test: runs-on: ubuntu-latest diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index a31ff4fb101f..ed8ad716b6e0 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -77,6 +77,14 @@ "options": { "cwd": "packages/twenty-server", "command": "node dist/src/queue-worker/queue-worker.js" + }, + "configurations": { + "ci": { + "env": { + "MESSAGE_QUEUE_TYPE": "sync", + "CACHE_STORAGE_TYPE": "memory" + } + } } }, "typeorm": { From d8c4af9279ab4c16fe0e47709bc7197c33486868 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 5 Oct 2024 00:22:38 +0200 Subject: [PATCH 12/16] Fix all broken CIs (#7439) Fix all the broken CIs :p This includes an ongoing effort to simplify test maintenance by having 1 unique source of truth about metadata and data mocks (that will later be generated from a unique source of seeds: dev = demo = test) Regressions: - Unit line coverage: 60 > 55 - Storybook Pages branch coverage: 40 > 35 We will need to write tests to increase those coverage - RelationFieldDisplay perf: 0.2ms to 0.22ms > We might have a regression here - Removed perf story about RawJSON > We will need to re-add it --- packages/twenty-emails/package.json | 2 +- packages/twenty-front/.storybook/preview.tsx | 8 +- packages/twenty-front/jest.config.ts | 3 +- packages/twenty-front/nyc.config.cjs | 2 +- packages/twenty-front/package.json | 2 +- .../useRightDrawerEmailThread.test.tsx | 436 ++++++- .../useActivityTargetObjectRecords.test.tsx | 2 +- .../__tests__/useCreateActivityInDB.test.tsx | 54 +- .../useOpenCreateActivityDrawer.test.tsx | 18 +- .../tasks/__stories__/TaskGroups.stories.tsx | 3 + .../tasks/__stories__/TaskList.stories.tsx | 11 +- .../tasks/components/TaskGroups.tsx | 1 + .../activities/tasks/components/TaskList.tsx | 1 + .../hooks/__tests__/useCompleteTask.test.tsx | 131 +- .../__tests__/useTimelineActivities.test.tsx | 9 +- .../EventRowMainObjectUpdated.stories.tsx | 6 +- .../ChromeExtensionSidecarProvider.tsx | 92 +- .../favorites/hooks/__mocks__/useFavorites.ts | 820 ++++++------- .../hooks/__tests__/useFavorites.test.tsx | 20 +- .../__tests__/useDefaultHomePagePath.test.ts | 2 +- .../ObjectMetadataItemsLoadEffect.tsx | 2 +- ...=> ApolloMetadataClientMockedProvider.tsx} | 0 .../hooks/__mocks__/useFieldMetadataItem.ts | 14 +- ...ColumnDefinitionsFromFieldMetadata.test.ts | 2 +- .../useCreateOneObjectMetadataItem.test.tsx | 14 +- .../__tests__/useFieldMetadataItem.test.tsx | 4 +- .../useFilteredObjectMetadataItems.test.tsx | 2 +- .../useGetObjectOrderByField.test.tsx | 12 +- ...ectRecordIdentifierByNameSingular.test.tsx | 2 +- .../__tests__/useGetRelationMetadata.test.tsx | 2 +- .../useMapToObjectRecordIdentifier.test.tsx | 8 +- .../__tests__/useObjectMetadataItem.test.tsx | 14 +- .../hooks/useObjectNamePluralFromSingular.ts | 2 +- .../hooks/useObjectNameSingularFromPlural.ts | 2 +- ...etObjectMetadataItemBySingularName.test.ts | 2 +- .../__tests__/getObjectOrderByField.test.ts | 2 +- .../utils/__tests__/getObjectSlug.test.ts | 2 +- ...ObjectMetadataAvailableForRelation.test.ts | 2 +- .../mapFieldMetadataToGraphQLQuery.test.tsx | 2 +- .../mapObjectMetadataToGraphQLQuery.test.tsx | 2 +- .../utils/mapObjectMetadataToGraphQLQuery.ts | 1 + .../objectMetadataItemSchema.test.ts | 7 +- .../__tests__/getRecordNodeFromRecord.test.ts | 40 +- .../cache/utils/getRecordNodeFromRecord.ts | 4 +- .../hooks/__mocks__/personFragment.ts | 48 - .../hooks/__mocks__/personFragments.ts | 327 +++++ .../hooks/__mocks__/useCreateManyRecords.ts | 4 +- .../hooks/__mocks__/useCreateOneRecord.ts | 4 +- .../hooks/__mocks__/useDeleteOneRecord.ts | 2 + .../__mocks__/useFindDuplicateRecords.ts | 4 +- .../hooks/__mocks__/useFindOneRecord.ts | 4 +- .../hooks/__mocks__/useUpdateOneRecord.ts | 4 +- .../__tests__/useCreateManyRecords.test.tsx | 14 +- .../useCreateManyRecordsMutation.test.tsx | 12 +- .../__tests__/useCreateOneRecord.test.tsx | 14 +- .../useCreateOneRecordMutation.test.tsx | 12 +- .../__tests__/useDeleteManyRecords.test.tsx | 14 +- .../useDeleteManyRecordsMutation.test.tsx | 8 +- .../__tests__/useDeleteOneRecord.test.tsx | 17 +- .../useDeleteOneRecordMutation.test.tsx | 10 +- .../__tests__/useFetchAllRecordIds.test.tsx | 27 +- .../useFindDuplicateRecords.test.tsx | 17 +- .../useFindDuplicateRecordsQuery.test.tsx | 12 +- .../__tests__/useFindManyRecords.test.tsx | 33 +- .../useFindManyRecordsQuery.test.tsx | 12 +- .../hooks/__tests__/useFindOneRecord.test.tsx | 25 +- .../__tests__/useFindOneRecordQuery.test.tsx | 12 +- ...ordsForMultipleMetadataItemsQuery.test.tsx | 2 +- .../__tests__/useLazyFindOneRecord.test.tsx | 17 +- .../__tests__/useObjectRecordTable.test.tsx | 19 +- .../__tests__/useUpdateOneRecord.test.tsx | 14 +- .../useUpdateOneRecordMutation.test.tsx | 12 +- .../hooks/useFindDuplicateRecords.ts | 2 +- .../object-record/hooks/useFindManyRecords.ts | 5 +- .../__mocks__/fieldDefinitions.ts | 27 +- .../hooks/__tests__/usePersistField.test.tsx | 19 +- .../__tests__/useToggleEditOnlyInput.test.tsx | 276 ++++- .../DateTimeFieldDisplay.perf.stories.tsx | 4 +- ...sx => EmailsFieldDisplay.perf.stories.tsx} | 27 +- .../perf/JsonFieldDisplay.perf.stories.tsx | 43 - .../MultiSelectFieldDisplay.perf.stories.tsx | 2 +- ...sx => PhonesFieldDisplay.perf.stories.tsx} | 20 +- .../perf/RatingFieldDisplay.perf.stories.tsx | 4 +- ...RelationToOneFieldDisplay.perf.stories.tsx | 2 +- .../isMatchingBooleanFilter.test.ts} | 0 .../isMatchingCurrencyFilter.test.ts} | 0 .../isMatchingDateFilter.test.ts} | 0 .../isMatchingFloatFilter.test.ts} | 0 .../isMatchingStringFilter.test.ts} | 0 .../isMatchingUUIDFilter.test.ts} | 0 .../isRecordMatchingFilter.test.ts} | 5 +- ...bjectDropdownFilterIntoQueryFilter.test.ts | 1063 +++++++++++++++++ ...turnObjectDropdownFilterIntoQueryFilter.ts | 52 - .../hooks/useLoadRecordIndexTable.ts | 4 + .../hooks/__tests__/useTableData.test.tsx | 120 +- .../RecordDetailRelationSection.stories.tsx | 10 +- .../perf/RecordTableCell.perf.stories.tsx | 2 +- .../hooks/__tests__/useUpsertRecord.test.tsx | 2 +- ...attedAsObjectRecordForSelectArray.test.tsx | 2 +- ...jectRecordsSpreasheetImportDialog.test.tsx | 274 ++++- .../useFilteredSearchEntityQuery.test.tsx | 2 +- ...AccountsCalendarChannelDetails.stories.tsx | 8 +- ...ccountsCalendarChannelsGeneral.stories.tsx | 8 +- ...sAccountsMessageChannelDetails.stories.tsx | 8 +- ...sDataModelFieldDescriptionForm.stories.tsx | 10 +- ...ngsDataModelFieldIconLabelForm.stories.tsx | 10 +- ...DataModelFieldSettingsFormCard.stories.tsx | 10 +- ...tingsDataModelFieldPreviewCard.stories.tsx | 37 +- .../__tests__/useFieldPreviewValue.test.tsx | 43 +- .../getCurrencyFieldPreviewValue.test.ts | 18 +- .../__tests__/getFieldPreviewValue.test.ts | 38 +- .../getMultiSelectFieldPreviewValue.test.ts | 50 +- .../getSelectFieldPreviewValue.test.ts | 30 +- ...ttingsDataModelObjectAboutForm.stories.tsx | 5 +- .../getFieldPreviewValueFromRecord.test.ts | 36 +- ...eServerlessFunctionUpdateFormState.test.ts | 4 +- .../useServerlessFunctionUpdateFormState.ts | 2 +- .../__stories__/RecordShowPage.stories.tsx | 8 - .../SettingsAppearance.stories.tsx | 36 +- .../SettingsIntegrationDatabase.stories.tsx | 3 +- .../decorators/ChipGeneratorsDecorator.tsx | 2 +- .../src/testing/decorators/PageDecorator.tsx | 53 +- .../src/testing/decorators/RootDecorator.tsx | 2 +- .../testing/decorators/getFieldDecorator.tsx | 4 +- .../twenty-front/src/testing/graphqlMocks.ts | 26 +- .../jest/JestObjectMetadataItemSetter.tsx | 2 +- .../jest/generateEmptyJestRecordNode.ts | 37 + ... getJestMetadataAndApolloMocksWrapper.tsx} | 2 +- .../generated/mock-metadata-query-result.ts | 539 ++++++++- ...ts => generatedMockObjectMetadataItems.ts} | 0 .../src/testing/mock-data/metadata.ts | 290 ----- .../src/testing/mock-data/users.ts | 2 + .../src/testing/mock-data/view-fields.ts | 218 +++- .../src/testing/mock-data/views.ts | 823 +------------ .../src/utils/format/__tests__/number.test.ts | 7 +- packages/twenty-server/jest.config.ts | 2 +- packages/twenty-server/package.json | 2 +- .../src/constants/assets-path.ts | 7 +- .../typeorm-seeds/metadata/fieldsMetadata.ts | 2 +- ...ld-metadata-to-graphql-query.utils.spec.ts | 3 + .../utils/__tests__/components.utils.spec.ts | 9 +- .../get-base-typescript-project-files.ts | 2 +- .../test/people.integration-spec.ts | 4 +- .../serverless-functions.integration-spec.ts | 61 - .../twenty-server/test/utils/setup-test.ts | 2 +- packages/twenty-ui/package.json | 2 +- .../display/icon/components/TablerIcons.ts | 2 +- packages/twenty-website/package.json | 2 +- 148 files changed, 4363 insertions(+), 2542 deletions(-) rename packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/{ApolloMetadataClientProvider.tsx => ApolloMetadataClientMockedProvider.tsx} (100%) delete mode 100644 packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts create mode 100644 packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts rename packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/{EmailFieldDisplay.perf.stories.tsx => EmailsFieldDisplay.perf.stories.tsx} (55%) delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx rename packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/{PhoneFieldDisplay.perf.stories.tsx => PhonesFieldDisplay.perf.stories.tsx} (61%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingBooleanFilter.spec.ts => __tests__/isMatchingBooleanFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingCurrencyFilter.spec.ts => __tests__/isMatchingCurrencyFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingDateFilter.spec.ts => __tests__/isMatchingDateFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingFloatFilter.spec.ts => __tests__/isMatchingFloatFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingStringFilter.spec.ts => __tests__/isMatchingStringFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isMatchingUUIDFilter.spec.ts => __tests__/isMatchingUUIDFilter.test.ts} (100%) rename packages/twenty-front/src/modules/object-record/record-filter/utils/{isRecordMatchingFilter.spec.ts => __tests__/isRecordMatchingFilter.test.ts} (98%) create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts create mode 100644 packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts rename packages/twenty-front/src/testing/jest/{getJestHookWrapper.tsx => getJestMetadataAndApolloMocksWrapper.tsx} (94%) rename packages/twenty-front/src/testing/mock-data/{objectMetadataItems.ts => generatedMockObjectMetadataItems.ts} (100%) delete mode 100644 packages/twenty-front/src/testing/mock-data/metadata.ts delete mode 100644 packages/twenty-server/test/serverless-functions.integration-spec.ts diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index a792a42a373a..1f20fbe7eb64 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -1,6 +1,6 @@ { "name": "twenty-emails", - "version": "0.31.canary", + "version": "0.31.0-canary", "description": "", "author": "", "private": true, diff --git a/packages/twenty-front/.storybook/preview.tsx b/packages/twenty-front/.storybook/preview.tsx index f49ed56c58b9..1d67634e2a54 100644 --- a/packages/twenty-front/.storybook/preview.tsx +++ b/packages/twenty-front/.storybook/preview.tsx @@ -1,7 +1,7 @@ -import { useEffect } from 'react'; import { ThemeProvider } from '@emotion/react'; import { Preview } from '@storybook/react'; import { initialize, mswDecorator } from 'msw-storybook-addon'; +import { useEffect } from 'react'; import { useDarkMode } from 'storybook-dark-mode'; import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui'; @@ -13,12 +13,16 @@ import 'react-loading-skeleton/dist/skeleton.css'; initialize({ onUnhandledRequest: async (request: Request) => { const fileExtensionsToIgnore = - /\.(ts|tsx|js|jsx|svg|css|png)(\?v=[a-zA-Z0-9]+)?/; + /\.(ts|tsx|js|jsx|svg|css|png|woff2)(\?v=[a-zA-Z0-9]+)?/; if (fileExtensionsToIgnore.test(request.url)) { return; } + if (request.url.startsWith('http://localhost:3000/files/data:image')) { + return; + } + const requestBody = await request.json(); // eslint-disable-next-line no-console console.warn(`Unhandled ${request.method} request to ${request.url} diff --git a/packages/twenty-front/jest.config.ts b/packages/twenty-front/jest.config.ts index c71df7b77aff..8ed7f398db4e 100644 --- a/packages/twenty-front/jest.config.ts +++ b/packages/twenty-front/jest.config.ts @@ -2,6 +2,7 @@ import { JestConfigWithTsJest, pathsToModuleNameMapper } from 'ts-jest'; // eslint-disable-next-line @typescript-eslint/no-var-requires const tsConfig = require('./tsconfig.json'); +process.env.TZ = 'GMT'; const jestConfig: JestConfigWithTsJest = { // to enable logs, comment out the following line @@ -25,7 +26,7 @@ const jestConfig: JestConfigWithTsJest = { coverageThreshold: { global: { statements: 60, - lines: 60, + lines: 55, functions: 50, }, }, diff --git a/packages/twenty-front/nyc.config.cjs b/packages/twenty-front/nyc.config.cjs index 3fbf2dfd6204..8ae501c6910f 100644 --- a/packages/twenty-front/nyc.config.cjs +++ b/packages/twenty-front/nyc.config.cjs @@ -16,7 +16,7 @@ const modulesCoverage = { }; const pagesCoverage = { - branches: 40, + branches: 35, statements: 60, lines: 60, functions: 45, diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 96e6442a3f56..9370f2460b92 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -1,6 +1,6 @@ { "name": "twenty-front", - "version": "0.31.canary", + "version": "0.31.0-canary", "private": true, "type": "module", "scripts": { diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx index f88c7b79a1b5..0af5cec8224a 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx @@ -1,56 +1,410 @@ -import { MockedProvider } from '@apollo/client/testing'; -import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; - -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { renderHook, waitFor } from '@testing-library/react'; +import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; +import gql from 'graphql-tag'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useRightDrawerEmailThread } from '../useRightDrawerEmailThread'; -jest.mock('@/object-record/hooks/useFindOneRecord', () => ({ - __esModule: true, - useFindOneRecord: jest.fn(), -})); +const mocks = [ + { + request: { + query: gql` + query FindOneMessageThread($objectRecordId: ID!) { + messageThread(filter: { id: { eq: $objectRecordId } }) { + __typename + id + } + } + `, + variables: { objectRecordId: '1' }, + }, + result: jest.fn(() => ({ + data: { + messageThread: { + id: '1', + __typename: 'MessageThread', + }, + }, + })), + }, + { + request: { + query: gql` + query FindManyMessages( + $filter: MessageFilterInput + $orderBy: [MessageOrderByInput] + $lastCursor: String + $limit: Int + ) { + messages( + filter: $filter + orderBy: $orderBy + first: $limit + after: $lastCursor + ) { + edges { + node { + __typename + createdAt + headerMessageId + id + messageParticipants { + edges { + node { + __typename + displayName + handle + id + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + role + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + } + } + } + messageThread { + __typename + id + } + receivedAt + subject + text + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } + `, + variables: { + filter: { messageThreadId: { eq: '1' } }, + orderBy: [{ receivedAt: 'AscNullsLast' }], + lastCursor: undefined, + limit: 10, + }, + }, + result: jest.fn(() => ({ + data: { + messages: { + edges: [ + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'message', + input: { + id: '1', + text: 'Message 1', + createdAt: '2024-10-03T10:20:10.145Z', + }, + }), + cursor: '1', + }, + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'message', + input: { + id: '2', + text: 'Message 2', + createdAt: '2024-10-03T10:20:10.145Z', + }, + }), + cursor: '2', + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '1', + endCursor: '2', + }, + }, + }, + })), + }, + { + request: { + query: gql` + query FindManyMessageParticipants( + $filter: MessageParticipantFilterInput + $orderBy: [MessageParticipantOrderByInput] + $lastCursor: String + $limit: Int + ) { + messageParticipants( + filter: $filter + orderBy: $orderBy + first: $limit + after: $lastCursor + ) { + edges { + node { + __typename + displayName + handle + id + messageId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + role + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } + `, + variables: { + filter: { messageId: { in: ['1', '2'] }, role: { eq: 'from' } }, + orderBy: undefined, + lastCursor: undefined, + limit: undefined, + }, + }, + result: jest.fn(() => ({ + data: { + messageParticipants: { + edges: [ + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'messageParticipant', + input: { + id: 'messageParticipant-1', + role: 'from', + messageId: '1', + }, + }), + cursor: '1', + }, + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'messageParticipant', + input: { + id: 'messageParticipant-2', + role: 'from', + messageId: '2', + }, + }), + cursor: '2', + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '1', + endCursor: '2', + }, + }, + }, + })), + }, +]; -jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ - __esModule: true, - useFindManyRecords: jest.fn(), -})); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, + onInitializeRecoilSnapshot: ({ set }) => { + set(viewableRecordIdState, '1'); + }, +}); describe('useRightDrawerEmailThread', () => { it('should return correct values', async () => { - const mockThread = { id: '1' }; - const mockMessages = [ - { id: '1', text: 'Message 1' }, - { id: '2', text: 'Message 2' }, + { + __typename: 'Message', + createdAt: '2024-10-03T10:20:10.145Z', + headerMessageId: '', + id: '1', + messageParticipants: [], + messageThread: null, + receivedAt: null, + sender: { + __typename: 'MessageParticipant', + displayName: '', + handle: '', + id: 'messageParticipant-1', + messageId: '1', + person: null, + role: 'from', + workspaceMember: null, + }, + subject: '', + text: 'Message 1', + }, + { + __typename: 'Message', + createdAt: '2024-10-03T10:20:10.145Z', + headerMessageId: '', + id: '2', + messageParticipants: [], + messageThread: null, + receivedAt: null, + sender: { + __typename: 'MessageParticipant', + displayName: '', + handle: '', + id: 'messageParticipant-2', + messageId: '2', + person: null, + role: 'from', + workspaceMember: null, + }, + subject: '', + text: 'Message 2', + }, ]; - const mockFetchMoreRecords = jest.fn(); - - (useFindOneRecord as jest.Mock).mockReturnValue({ - record: mockThread, - loading: false, - fetchMoreRecords: mockFetchMoreRecords, - }); - - (useFindManyRecords as jest.Mock).mockReturnValue({ - records: mockMessages, - loading: false, - fetchMoreRecords: mockFetchMoreRecords, - }); - const { result } = renderHook(() => useRightDrawerEmailThread(), { - wrapper: ({ children }) => ( - - {children} - - ), + wrapper: Wrapper, }); - expect(result.current.thread).toBeDefined(); - expect(result.current.messages).toEqual(mockMessages); - expect(result.current.threadLoading).toBeFalsy(); - expect(result.current.fetchMoreMessages).toBeInstanceOf(Function); + await waitFor(() => { + expect(result.current.thread).toBeDefined(); + expect(result.current.messages).toEqual(mockMessages); + expect(result.current.threadLoading).toBeFalsy(); + expect(result.current.fetchMoreMessages).toBeInstanceOf(Function); + }); }); }); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index 260e931a3ae2..7d1426f4cb75 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -9,7 +9,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; const cache = new InMemoryCache(); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx index 1b56bc49d5da..baddb1029bda 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx @@ -1,13 +1,11 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import gql from 'graphql-tag'; import pick from 'lodash.pick'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { mockedTasks } from '~/testing/mock-data/tasks'; const mockedDate = '2024-03-15T12:00:00.000Z'; @@ -26,14 +24,44 @@ const mocks: MockedResponse[] = [ mutation CreateOneTask($input: TaskCreateInput!) { createTask(data: $input) { __typename - updatedAt + assignee { + __typename + id + name { + firstName + lastName + } + } + assigneeId + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + body createdAt dueAt id status - body - assigneeId title + updatedAt } } `, @@ -56,15 +84,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateActivityInDB', () => { it('Should create activity in DB', async () => { diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx index 665702704d1b..855c0b55bd29 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx @@ -1,7 +1,6 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -9,7 +8,8 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import gql from 'graphql-tag'; import pick from 'lodash.pick'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedTasks } from '~/testing/mock-data/tasks'; const mockedDate = '2024-03-15T12:00:00.000Z'; @@ -61,13 +61,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const mockObjectMetadataItems = generatedMockObjectMetadataItems; diff --git a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx index 444049cc1844..aefa6f2ed59a 100644 --- a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx @@ -42,5 +42,8 @@ export const WithTasks: Story = { }, parameters: { msw: graphqlMocks, + container: { + width: '500px', + }, }, }; diff --git a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx index 1113febb6223..c2a65b772be9 100644 --- a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx @@ -3,6 +3,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { TaskList } from '@/activities/tasks/components/TaskList'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { mockedTasks } from '~/testing/mock-data/tasks'; @@ -10,13 +11,21 @@ import { mockedTasks } from '~/testing/mock-data/tasks'; const meta: Meta = { title: 'Modules/Activity/TaskList', component: TaskList, - decorators: [MemoryRouterDecorator, ComponentDecorator, SnackBarDecorator], + decorators: [ + ComponentDecorator, + MemoryRouterDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { title: 'Tasks', tasks: mockedTasks, }, parameters: { msw: graphqlMocks, + container: { + width: '500px', + }, }, }; diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx index ae484382ffcc..16ebbec0f38a 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx @@ -27,6 +27,7 @@ import { TaskList } from './TaskList'; const StyledContainer = styled.div` display: flex; flex-direction: column; + width: 100%; `; type TaskGroupsProps = { diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx index 04e56c8d65ed..10e7982ca04f 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx @@ -12,6 +12,7 @@ type TaskListProps = { const StyledContainer = styled.div` align-items: flex-start; + width: 100%; align-self: stretch; display: flex; flex-direction: column; diff --git a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx index ba3e2ac53482..814b72fb6ce1 100644 --- a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx @@ -1,11 +1,10 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import gql from 'graphql-tag'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useCompleteTask } from '@/activities/tasks/hooks/useCompleteTask'; import { Task } from '@/activities/types/Task'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const task: Task = { id: '123', @@ -28,21 +27,123 @@ const mocks: MockedResponse[] = [ mutation UpdateOneTask($idToUpdate: ID!, $input: TaskUpdateInput!) { updateTask(id: $idToUpdate, data: $input) { __typename - updatedAt - createdAt - deletedAt - dueAt - id - status + assignee { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + assigneeId + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } body + createdAt createdBy { source workspaceMemberId name } - assigneeId + deletedAt + dueAt + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id position + status + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } title + updatedAt } } `, @@ -72,13 +173,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCompleteTask', () => { it('should complete task', async () => { diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx index 894d541c888b..2d1989cc68ea 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx @@ -1,21 +1,16 @@ import { renderHook } from '@testing-library/react'; import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities'; -import { ReactNode } from 'react'; -import { getJestHookWrapper } from '~/testing/jest/getJestHookWrapper'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: jest.fn(), })); -const Wrappers = getJestHookWrapper({ +const Wrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [], }); -const Wrapper = ({ children }: { children: ReactNode }) => ( - {children} -); - describe('useTimelineActivities', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx index 0cb945bdc4b2..224438b853cf 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx @@ -5,7 +5,7 @@ import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/ import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const meta: Meta = { title: 'Modules/TimelineActivities/Rows/MainObject/EventRowMainObjectUpdated', @@ -35,7 +35,9 @@ const meta: Meta = { }, }, } as TimelineActivity, - mainObjectMetadataItem: mockedPersonObjectMetadataItem, + mainObjectMetadataItem: generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', + ), }, decorators: [ ComponentDecorator, diff --git a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx index 3c4d8556a4bf..9409540ead23 100644 --- a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx +++ b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx @@ -1,56 +1,46 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - -import { isLoadingTokensFromExtensionState } from '@/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState'; -import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; -import { isDefined } from '~/utils/isDefined'; -import { isInFrame } from '~/utils/isInIframe'; - -const StyledContainer = styled.div` - align-items: center; - display: flex; - flex-direction: column; - height: 100vh; - justify-content: center; -`; - -const AppInaccessible = ({ message }: { message: string }) => { - return ( - - twenty-icon -

{message}

-
- ); -}; +// const StyledContainer = styled.div` +// align-items: center; +// display: flex; +// flex-direction: column; +// height: 100vh; +// justify-content: center; +// `; + +// const AppInaccessible = ({ message }: { message: string }) => { +// return ( +// +// twenty-icon +//

{message}

+//
+// ); +// }; export const ChromeExtensionSidecarProvider: React.FC< React.PropsWithChildren > = ({ children }) => { - const isLoadingTokensFromExtension = useRecoilValue( - isLoadingTokensFromExtensionState, - ); - const chromeExtensionId = useRecoilValue(chromeExtensionIdState); - - if (!isInFrame()) return <>{children}; - - if (!isDefined(chromeExtensionId)) - return ( - - ); - - if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) - return ( - - ); - - return isLoadingTokensFromExtension && <>{children}; + return <>{children}; + + // TODO: this is conflictting with storybook tests + // if (!isInFrame()) return <>{children}; + + // if (!isDefined(chromeExtensionId)) + // return ( + // + // ); + + // if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) + // return ( + // + // ); + + // return isLoadingTokensFromExtension && <>{children}; }; diff --git a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts index 98813ad384c7..cf0fe8e888b1 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts @@ -87,262 +87,264 @@ export const mocks = [ mutation CreateOneFavorite($input: FavoriteCreateInput!) { createFavorite(data: $input) { __typename - noteId - taskId - person { + company { __typename - name { - firstName - lastName + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks } linkedinLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - deletedAt - createdAt + name + position + tagline updatedAt - jobTitle - intro - workPrefereance - performanceRating + visaSponsorship + workPolicy xLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones + } + companyId + createdAt + deletedAt + id + note { + __typename + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + position + title + updatedAt + } + noteId + opportunity { + __typename + amount { + amountMicros + currencyCode } + closeDate + companyId + createdAt createdBy { source workspaceMemberId name } + deletedAt id + name + pointOfContactId position + stage + updatedAt + } + opportunityId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt emails { primaryEmail additionalEmails } - avatarUrl + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt whatsapp { primaryPhoneNumber primaryPhoneCountryCode additionalPhones } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } - task { + personId + position + rocket { __typename - updatedAt createdAt - deletedAt - dueAt - id - status - body createdBy { source workspaceMemberId name } - assigneeId - position - title - } - rocketId - viewId - updatedAt - workflowId - personId - workspaceMemberId - note { - __typename deletedAt id + name position updatedAt + } + rocketId + task { + __typename + assigneeId + body + createdAt createdBy { source workspaceMemberId name } - body + deletedAt + dueAt + id + position + status title - createdAt + updatedAt } - createdAt + taskId + updatedAt view { __typename - id - type + createdAt + deletedAt icon - key + id isCompact kanbanFieldMetadataId + key + name objectMetadataId position - createdAt - deletedAt + type updatedAt - name } - opportunityId - position - deletedAt - id - companyId + viewId workflow { __typename - deletedAt - lastPublishedVersionId createdAt + deletedAt id - statuses + lastPublishedVersionId name position + statuses updatedAt } + workflowId workspaceMember { __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale name { firstName lastName } - avatarUrl - userId - createdAt - timeZone - id timeFormat + timeZone updatedAt - locale userEmail - deletedAt - colorScheme - dateFormat + userId } - company { - __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - visaSponsorship - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } - position - employees - deletedAt - accountOwnerId - annualRecurringRevenue { - amountMicros - currencyCode - } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - createdAt - createdBy { - source - workspaceMemberId - name - } - workPolicy - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - tagline - idealCustomerProfile - } - rocket { - __typename - createdBy { - source - workspaceMemberId - name - } - updatedAt - name - position - createdAt - id - deletedAt - } - opportunity { - __typename - createdBy { - source - workspaceMemberId - name - } - amount { - amountMicros - currencyCode - } - stage - position - closeDate - id - name - pointOfContactId - companyId - updatedAt - deletedAt - createdAt - } - } - } - `, - variables: { - input: { - id: mockId, - personId: favoriteTargetObjectId, - position: 4, - workspaceMemberId: '1', - }, - }, - }, - result: jest.fn(() => ({ - data: { - createFavorite: { - id: favoriteId, - }, - }, - })), - }, - { - request: { - query: gql` - mutation DeleteOneFavorite($idToDelete: ID!) { - deleteFavorite(id: $idToDelete) { - id + workspaceMemberId + } + } + `, + variables: { + input: { + id: mockId, + personId: favoriteTargetObjectId, + position: 4, + workspaceMemberId: '1', + }, + }, + }, + result: jest.fn(() => ({ + data: { + createFavorite: { + id: favoriteId, + }, + }, + })), + }, + { + request: { + query: gql` + mutation DeleteOneFavorite($idToDelete: ID!) { + deleteFavorite(id: $idToDelete) { + __typename + deletedAt + id } } `, @@ -365,236 +367,236 @@ export const mocks = [ ) { updateFavorite(id: $idToUpdate, data: $input) { __typename - noteId - taskId - person { - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - } - task { - __typename - updatedAt - createdAt - deletedAt - dueAt - id - status - body - createdBy { - source - workspaceMemberId - name - } - assigneeId - position - title - } - rocketId - viewId - updatedAt - workflowId - personId - workspaceMemberId - note { - __typename - deletedAt - id - position - updatedAt - createdBy { - source - workspaceMemberId - name - } - body - title - createdAt - } - createdAt - view { - __typename - id - type - icon - key - isCompact - kanbanFieldMetadataId - objectMetadataId - position - createdAt - deletedAt - updatedAt - name - } - opportunityId - position - deletedAt - id - companyId - workflow { - __typename - deletedAt - lastPublishedVersionId - createdAt - id - statuses - name - position - updatedAt - } - workspaceMember { - __typename - name { - firstName - lastName - } - avatarUrl - userId - createdAt - timeZone - id - timeFormat - updatedAt - locale - userEmail - deletedAt - colorScheme - dateFormat - } - company { - __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - visaSponsorship - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } - position - employees - deletedAt - accountOwnerId - annualRecurringRevenue { - amountMicros - currencyCode - } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - createdAt - createdBy { - source - workspaceMemberId - name - } - workPolicy - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - tagline - idealCustomerProfile - } - rocket { - __typename - createdBy { - source - workspaceMemberId - name - } - updatedAt - name - position - createdAt - id - deletedAt - } - opportunity { - __typename - createdBy { - source + company { + __typename + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name + position + tagline + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + companyId + createdAt + deletedAt + id + note { + __typename + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + position + title + updatedAt + } + noteId + opportunity { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + opportunityId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + personId + position + rocket { + __typename + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + position + updatedAt + } + rocketId + task { + __typename + assigneeId + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + dueAt + id + position + status + title + updatedAt + } + taskId + updatedAt + view { + __typename + createdAt + deletedAt + icon + id + isCompact + kanbanFieldMetadataId + key + name + objectMetadataId + position + type + updatedAt + } + viewId + workflow { + __typename + createdAt + deletedAt + id + lastPublishedVersionId + name + position + statuses + updatedAt + } + workflowId + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } workspaceMemberId - name - } - amount { - amountMicros - currencyCode - } - stage - position - closeDate - id - name - pointOfContactId - companyId - updatedAt - deletedAt - createdAt - } } } `, diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx index 3a30077ea285..9c984ececa94 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx @@ -1,16 +1,14 @@ -import { MockedProvider } from '@apollo/client/testing'; import { DropResult, ResponderProvided } from '@hello-pangea/dnd'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { favoriteId, favoriteTargetObjectRecord, @@ -29,15 +27,9 @@ jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: () => ({ records: initialFavorites }), })); -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFavorites', () => { it('should fetch favorites successfully', async () => { diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index 1ec02c2fd071..a7fb3b4e51ed 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -6,7 +6,7 @@ import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePat import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { AppPath } from '@/types/AppPath'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedUserData } from '~/testing/mock-data/users'; jest.mock('@/prefetch/hooks/usePrefetchedData'); diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx index dd1971790bb1..c8659e1689c8 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx @@ -9,7 +9,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider.tsx similarity index 100% rename from packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider.tsx rename to packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider.tsx diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts index de52cbd8fe73..0e7470a10359 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts @@ -48,10 +48,20 @@ export const queries = { createMetadataField: gql` mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) { createOneField(input: $input) { - ${baseFields} + id + type + name + label + description + icon + isCustom + isActive + isNullable + createdAt + updatedAt + settings defaultValue options - settings } } `, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts index 558d2ff3034c..05c87497dcc6 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts @@ -3,7 +3,7 @@ import { Nullable } from 'twenty-ui'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useColumnDefinitionsFromFieldMetadata', () => { it('should return empty definitions if no object is passed', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx index 03443712d9de..cc92f241e31c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx @@ -1,10 +1,8 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { findManyViewsQuery, query, @@ -47,13 +45,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateOneObjectMetadataItem', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx index 47b1a6c18c88..6795897e3cfd 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx @@ -1,6 +1,6 @@ -import { ReactNode } from 'react'; import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; +import { act, ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx index 4d16bb6d8af9..02e39712fe57 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx @@ -10,7 +10,7 @@ import { } from '@/object-metadata/hooks/__mocks__/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx index 21ec56ad60b4..41220e695b94 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx @@ -1,15 +1,11 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useGetObjectOrderByField } from '@/object-metadata/hooks/useGetObjectOrderByField'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); describe('useGetObjectOrderByField', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx index a457bd27eff7..938796b78714 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx @@ -3,7 +3,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useGetObjectRecordIdentifierByNameSingular', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx index 12ef572b6adf..7455088f27bc 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx @@ -5,7 +5,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx index d0157678c04a..8792fb31299a 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx @@ -1,7 +1,11 @@ import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); describe('useMapToObjectRecordIdentifier', () => { it('should work as expected', async () => { @@ -18,7 +22,7 @@ describe('useMapToObjectRecordIdentifier', () => { }); }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx index ee757d7ff7b2..876846f2bd4b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx @@ -1,16 +1,12 @@ -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); // Split into tests for each new hook describe('useObjectMetadataItem', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts index 25208307eabd..88ac0baab21d 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNamePluralFromSingular = ({ diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts index 5a94a5191b9f..2e5127d8fcd9 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNameSingularFromPlural = ({ diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts index ea2f1d5eee19..92a7e414b822 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts @@ -1,5 +1,5 @@ import { getObjectMetadataItemByNameSingular } from '@/object-metadata/utils/getObjectMetadataItemBySingularName'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectMetadataItemBySingularName', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts index 78c0e7047a4a..cc5691412fd3 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts @@ -1,5 +1,5 @@ import { getOrderByFieldForObjectMetadataItem } from '@/object-metadata/utils/getObjectOrderByField'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectOrderByField', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts index c43c9d039e7c..526c6fe47635 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts @@ -1,5 +1,5 @@ import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectSlug', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts index caefb7feed45..ece3ae39de06 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts @@ -1,5 +1,5 @@ import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('isObjectMetadataAvailableForRelation', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx index 755ce29ac677..7208246e7e49 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx @@ -1,5 +1,5 @@ import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLField } from '~/utils/normalizeGQLField'; const personObjectMetadataItem = generatedMockObjectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx index c47eae446715..d2650b69807f 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx @@ -1,5 +1,5 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; const personObjectMetadataItem = generatedMockObjectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts index f9e248815d99..9e705d428ced 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -18,6 +18,7 @@ export const mapObjectMetadataToGraphQLQuery = ({ const fieldsThatShouldBeQueried = objectMetadataItem?.fields .filter((field) => field.isActive) + .sort((fieldA, fieldB) => fieldA.name.localeCompare(fieldB.name)) .filter((field) => shouldFieldBeQueried({ field, diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts index 0e8d60c66fb9..cbb1b2c46b3d 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts @@ -1,11 +1,12 @@ -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; - +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { objectMetadataItemSchema } from '../objectMetadataItemSchema'; describe('objectMetadataItemSchema', () => { it('validates a valid object metadata item', () => { // Given - const validObjectMetadataItem = mockedCompanyObjectMetadataItem; + const validObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); // When const result = objectMetadataItemSchema.parse(validObjectMetadataItem); diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts index 219d05f854e5..4641974a0632 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts @@ -1,10 +1,8 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - mockedObjectMetadataItems, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; + import { getPeopleMock } from '~/testing/mock-data/people'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord'; const peopleMock = getPeopleMock(); @@ -12,11 +10,18 @@ const peopleMock = getPeopleMock(); describe('getRecordNodeFromRecord', () => { it('computes relation records cache references by default', () => { // Given - const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; - const objectMetadataItem: Pick< - ObjectMetadataItem, - 'fields' | 'namePlural' | 'nameSingular' - > = mockedPersonObjectMetadataItem; + const objectMetadataItems: ObjectMetadataItem[] = + generatedMockObjectMetadataItems; + const objectMetadataItem: + | Pick + | undefined = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + const recordGqlFields = { name: true, company: true, @@ -47,11 +52,18 @@ describe('getRecordNodeFromRecord', () => { it('does not compute relation records cache references when `computeReferences` is false', () => { // Given - const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; - const objectMetadataItem: Pick< - ObjectMetadataItem, - 'fields' | 'namePlural' | 'nameSingular' - > = mockedPersonObjectMetadataItem; + const objectMetadataItems: ObjectMetadataItem[] = + generatedMockObjectMetadataItems; + const objectMetadataItem: + | Pick + | undefined = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + const recordGqlFields = { name: true, company: true, diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts index d7faf5a77f29..2ab3b25344fa 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts @@ -65,7 +65,9 @@ export const getRecordNodeFromRecord = ({ RelationDefinitionType.OneToMany ) { const oneToManyObjectMetadataItem = objectMetadataItems.find( - (item) => item.namePlural === fieldName, + (item) => + item.namePlural === + field.relationDefinition?.targetObjectMetadata.namePlural, ); if (!oneToManyObjectMetadataItem) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts deleted file mode 100644 index 8a2b3f08567d..000000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const PERSON_FRAGMENT = ` - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } -` diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts new file mode 100644 index 000000000000..21cf8b2848b2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts @@ -0,0 +1,327 @@ +export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = ` + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink{ + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } +` + +export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` + __typename + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } + } + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + avatarUrl + calendarEventParticipants { + edges { + node { + __typename + calendarEventId + createdAt + deletedAt + displayName + handle + id + isOrganizer + personId + responseStatus + updatedAt + workspaceMemberId + } + } + } + city + company { + __typename + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name + position + tagline + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + messageParticipants { + edges { + node { + __typename + createdAt + deletedAt + displayName + handle + id + messageId + personId + role + updatedAt + workspaceMemberId + } + } + } + name { + firstName + lastName + } + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + pointOfContactForOpportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + position + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } +` diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts index fd28307168b5..b9d5b32b8b36 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts @@ -1,12 +1,12 @@ import { gql } from '@apollo/client'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { Person } from '@/people/types/Person'; export const query = gql` mutation CreatePeople($data: [PersonCreateInput!]!, $upsert: Boolean) { createPeople(data: $data, upsert: $upsert) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts index 6804a92c3525..6a0261794f3c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts @@ -1,10 +1,10 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; export const query = gql` mutation CreateOnePerson($input: PersonCreateInput!) { createPerson(data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts index f31b210e089b..2e7ce9bc5320 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts @@ -3,6 +3,8 @@ import { gql } from '@apollo/client'; export const query = gql` mutation DeleteOnePerson($idToDelete: ID!) { deletePerson(id: $idToDelete) { + __typename + deletedAt id } } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts index 2e50e02aac84..784e178fc785 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts @@ -1,4 +1,4 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; import { getPeopleMock } from '~/testing/mock-data/people'; @@ -9,7 +9,7 @@ export const query = gql` personDuplicates(ids: $ids) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts index 26ac2981aa37..075dabb052f5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts @@ -1,12 +1,12 @@ import { gql } from '@apollo/client'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { responseData as person } from './useUpdateOneRecord'; export const query = gql` query FindOnePerson($objectRecordId: ID!) { person(filter: { id: { eq: $objectRecordId } }) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts index 15bb27bc76b1..1c109b9e3ed4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts @@ -1,10 +1,10 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; export const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx index 9eb14eadd799..761680c43081 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx @@ -1,8 +1,5 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { mocked } from '@storybook/test'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { v4 } from 'uuid'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -12,6 +9,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useCreateManyRecords'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('uuid', () => ({ v4: jest.fn(), @@ -37,13 +35,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateManyRecords', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx index 3b4d1ad42d4c..a5ab3efec117 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation CreatePeople($data: [PersonCreateInput!]!, $upsert: Boolean) { createPeople(data: $data, upsert: $upsert) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useCreateManyRecordsMutation', () => { it('should return a valid createManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useCreateManyRecordsMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx index 837af4dd2bf6..db26b860517a 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { @@ -9,6 +6,7 @@ import { responseData, } from '@/object-record/hooks/__mocks__/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9'; const input = { name: { firstName: 'John', lastName: 'Doe' } }; @@ -31,13 +29,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateOneRecord', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx index 18db35f815b0..376a085cde7c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation CreateOnePerson($input: PersonCreateInput!) { createPerson(data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useCreateOneRecordMutation', () => { it('should return a valid createOneRecordMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useCreateOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx index 1c7dd33d33e6..ada53864a7a6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx @@ -1,7 +1,4 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,6 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const people = [ 'a7286b9a-c039-4a89-9567-2dfa7953cda9', @@ -29,13 +27,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useDeleteManyRecords', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx index 7f96b1be6114..cc5b85d6b664 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx @@ -1,8 +1,8 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation DeleteManyPeople($filter: PersonFilterInput!) { @@ -12,6 +12,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useDeleteManyRecordsMutation', () => { it('should return a valid deleteManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -22,7 +26,7 @@ describe('useDeleteManyRecordsMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx index 731a468a2835..0c347d309520 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx @@ -1,7 +1,5 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; import { query, @@ -9,6 +7,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9'; @@ -26,13 +25,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useDeleteOneRecord', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx index 3bbc51f65a85..859355818ae8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx @@ -1,17 +1,23 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation DeleteOnePerson($idToDelete: ID!) { deletePerson(id: $idToDelete) { + __typename + deletedAt id } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useDeleteOneRecordMutation', () => { it('should return a valid deleteOneRecordMutation', () => { const objectNameSingular = 'person'; @@ -22,7 +28,7 @@ describe('useDeleteOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx index 02095502e9ed..80b57d7dc5bd 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx @@ -1,7 +1,6 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode, useEffect } from 'react'; -import { RecoilRoot, useRecoilState } from 'recoil'; +import { useEffect } from 'react'; +import { useRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { @@ -16,8 +15,8 @@ import { variablesThirdRequest, } from '@/object-record/hooks/__mocks__/useFetchAllRecordIds'; import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { @@ -52,22 +51,12 @@ const mocks = [ }, ]; +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + describe('useFetchAllRecordIds', () => { it('fetches all record ids with fetch more synchronous loop', async () => { - const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - - ); - const { result } = renderHook( () => { const [, setObjectMetadataItems] = useRecoilState( diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx index e8616d1da1a7..61cd950ff76f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx @@ -1,11 +1,8 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useFindDuplicateRecords } from '@/object-record/hooks/useFindDuplicateRecords'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { query, responseData, @@ -24,15 +21,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFindDuplicateRecords', () => { it('should fetch duplicate records and return the correct data', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx index e7d0ad73348f..10cb78a400d3 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx @@ -1,16 +1,16 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindDuplicatePerson($ids: [ID!]!) { personDuplicates(ids: $ids) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -23,6 +23,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindDuplicateRecordsQuery', () => { it('should return a valid findDuplicateRecordsQuery', () => { const objectNameSingular = 'person'; @@ -33,7 +37,7 @@ describe('useFindDuplicateRecordsQuery', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx index d86769f1d219..5d85b80c98da 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx @@ -1,7 +1,5 @@ -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -11,8 +9,8 @@ import { variables, } from '@/object-record/hooks/__mocks__/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { @@ -28,29 +26,10 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); - +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFindManyRecords', () => { - it('should skip fetch if currentWorkspaceMember is undefined', async () => { - const { result } = renderHook( - () => useFindManyRecords({ objectNameSingular: 'person' }), - { - wrapper: Wrapper, - }, - ); - - expect(result.current.loading).toBe(false); - expect(result.current.error).toBeUndefined(); - }); - it('should work as expected', async () => { const onCompleted = jest.fn(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx index 3d2213a589f4..0c7fd0afbeb2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx @@ -1,16 +1,16 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) { people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -25,6 +25,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindManyRecordsQuery', () => { it('should return a valid findManyRecordsQuery', () => { const objectNameSingular = 'person'; @@ -37,7 +41,7 @@ describe('useFindManyRecordsQuery', () => { computeReferences, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx index 62e11efe51dd..e7249d9caf4b 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx @@ -1,15 +1,12 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { query, - responseData, variables, } from '@/object-record/hooks/__mocks__/useFindOneRecord'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const mocks = [ { @@ -19,21 +16,19 @@ const mocks = [ }, result: jest.fn(() => ({ data: { - person: responseData, + person: generateEmptyJestRecordNode({ + objectNameSingular: 'person', + input: { id: '6205681e-7c11-40b4-9e32-f523dbe54590' }, + withDepthOneRelation: true, + }), }, })), }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const objectRecordId = '6205681e-7c11-40b4-9e32-f523dbe54590'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx index 386e0d55f84f..32b2a169139d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindOnePerson($objectRecordId: ID!) { person(filter: { id: { eq: $objectRecordId } }) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindOneRecordQuery', () => { it('should return a valid findOneRecordQuery', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useFindOneRecordQuery', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx index feb33c81b30a..2cdf074ad1d5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx @@ -5,7 +5,7 @@ import { RecoilRoot } from 'recoil'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx index 013889b934cd..77833552d8db 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,7 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useFindOneRecord'; import { useLazyFindOneRecord } from '@/object-record/hooks/useLazyFindOneRecord'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const mocks = [ { @@ -25,15 +22,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const objectRecordId = '6205681e-7c11-40b4-9e32-f523dbe54590'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx index 627991b7f41d..4cd4cdbffc11 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx @@ -1,13 +1,12 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { expect } from '@storybook/test'; import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; +import { ReactNode } from 'react'; +import { mocks } from '@/auth/hooks/__mocks__/useAuth'; import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable'; import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const recordTableId = 'people'; const objectNameSingular = 'person'; @@ -17,20 +16,22 @@ const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => { return <>{children}; }; +const HookMockWrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + const Wrapper = ({ children }: { children: ReactNode }) => { return ( - + - - {children} - + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx index eb6e7048d316..d32ef37508b1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,6 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const person = { id: '36abbb63-34ed-4a16-89f5-f549ac55d0f9' }; const update = { @@ -37,13 +35,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const idToUpdate = '36abbb63-34ed-4a16-89f5-f549ac55d0f9'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx index 7581e1612582..be862743e9b8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; const expectedQueryTemplate = ` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } }`; +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useUpdateOneRecordMutation', () => { it('should return a valid createManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useUpdateOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts index bd75db4a75a4..7b73f918fd74 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; import { useQuery } from '@apollo/client'; +import { useMemo } from 'react'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index c5f22c9f640e..0dac0caa6d68 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -1,7 +1,5 @@ import { useQuery, WatchQueryFetchPolicy } from '@apollo/client'; -import { useRecoilValue } from 'recoil'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult'; @@ -36,7 +34,6 @@ export const useFindManyRecords = ({ onCompleted, cursorFilter, }: UseFindManyRecordsParams) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); @@ -66,7 +63,7 @@ export const useFindManyRecords = ({ const { data, loading, error, fetchMore } = useQuery(findManyRecordsQuery, { - skip: skip || !objectMetadataItem || !currentWorkspaceMember, + skip: skip || !objectMetadataItem, variables: { filter, orderBy, diff --git a/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts b/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts index e688a2a64544..90cd7f176e26 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts @@ -9,10 +9,8 @@ import { FieldTextMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + export const fieldMetadataId = 'fieldMetadataId'; export const textfieldDefinition: FieldDefinition = { @@ -24,7 +22,16 @@ export const textfieldDefinition: FieldDefinition = { metadata: { placeHolder: 'John Doe', fieldName: 'userName' }, }; -const relationFieldMetadataItem = mockedPersonObjectMetadataItem.fields?.find( +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + ({ nameSingular }) => nameSingular === 'person', +); + +if (!mockedPersonObjectMetadataItem) { + throw new Error('Person object metadata item not found'); +} + + +const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find( ({ name }) => name === 'company', ); @@ -91,7 +98,15 @@ export const ratingFieldDefinition: FieldDefinition = { }, }; -const booleanFieldMetadataItem = mockedCompanyObjectMetadataItem.fields?.find( +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + +const booleanFieldMetadataItem = mockedCompanyObjectMetadataItem?.fields?.find( ({ name }) => name === 'idealCustomerProfile', ); export const booleanFieldDefinition = formatFieldMetadataItemAsFieldDefinition({ diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx index 2e8756c2e8f0..4eea3aae834f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx @@ -1,11 +1,11 @@ import { gql } from '@apollo/client'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook, waitFor } from '@testing-library/react'; import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { phonesFieldDefinition, @@ -20,11 +20,12 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; @@ -72,6 +73,10 @@ const mocks: MockedResponse[] = [ const recordId = 'recordId'; +const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + const getWrapper = (fieldDefinition: FieldDefinition) => ({ children }: { children: ReactNode }) => { @@ -91,7 +96,7 @@ const getWrapper = }; return ( - + - {children} + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx index 86a2037c5d2c..5ec442483de4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx @@ -1,8 +1,7 @@ import { gql } from '@apollo/client'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; +import { MockedResponse } from '@apollo/client/testing'; +import { renderHook, waitFor } from '@testing-library/react'; +import { ReactNode, act } from 'react'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; @@ -13,6 +12,8 @@ import { RecordUpdateHookParams, } from '@/object-record/record-field/contexts/FieldContext'; import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const recordId = 'recordId'; @@ -26,13 +27,42 @@ const mocks: MockedResponse[] = [ ) { updateCompany(id: $idToUpdate, data: $input) { __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + accountOwner { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + accountOwnerId + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } } - visaSponsorship address { addressStreet1 addressStreet2 @@ -43,20 +73,31 @@ const mocks: MockedResponse[] = [ addressLat addressLng } - position - employees - deletedAt - accountOwnerId annualRecurringRevenue { amountMicros currencyCode } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } } createdAt createdBy { @@ -64,7 +105,36 @@ const mocks: MockedResponse[] = [ workspaceMemberId name } - workPolicy + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + idealCustomerProfile introVideo { primaryLinkUrl primaryLinkLabel @@ -75,8 +145,151 @@ const mocks: MockedResponse[] = [ primaryLinkLabel secondaryLinks } + name + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + opportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + people { + edges { + node { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + } + } + position tagline - idealCustomerProfile + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } } `, @@ -87,8 +300,12 @@ const mocks: MockedResponse[] = [ }, result: jest.fn(() => ({ data: { - updateWorkspaceMember: { - id: 'recordId', + updateCompany: { + ...generateEmptyJestRecordNode({ + objectNameSingular: CoreObjectNameSingular.Company, + input: { id: recordId }, + withDepthOneRelation: true, + }), }, }, })), @@ -111,8 +328,13 @@ const Wrapper = ({ children }: { children: ReactNode }) => { return [updateEntity, { loading: false }]; }; + const JestMetadataAndApolloMocksWrapper = + getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, + }); + return ( - + { useUpdateRecord: useUpdateOneRecordMutation, }} > - {children} + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx index 079b84520e91..9e2cb6b78622 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx @@ -51,6 +51,6 @@ export const Elipsis: Story = { export const Performance = getProfilingStory({ componentName: 'DateTimeFieldDisplay', averageThresholdInMs: 0.1, - numberOfRuns: 50, - numberOfTestsPerRun: 100, + numberOfRuns: 30, + numberOfTestsPerRun: 30, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx similarity index 55% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx index b901caa9c1cd..283224dd88a5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx @@ -1,19 +1,22 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; -import { EmailFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailFieldDisplay'; +import { EmailsFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailsFieldDisplay'; 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', + title: 'UI/Data/Field/Display/EmailsFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('person', 'email'), + getFieldDecorator('person', 'emails', { + primaryEmail: 'test@test.com', + additionalEmails: ['toto@test.com'], + }), ComponentDecorator, ], - component: EmailFieldDisplay, + component: EmailsFieldDisplay, args: {}, parameters: { chromatic: { disableSnapshot: true }, @@ -22,25 +25,25 @@ const meta: Meta = { export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; export const Elipsis: Story = { parameters: { - container: { width: 50 }, + container: { width: 100 }, }, decorators: [ - getFieldDecorator( - 'person', - 'email', - 'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com', - ), + getFieldDecorator('person', 'emails', { + primaryEmail: + 'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com', + additionalEmails: [], + }), ], }; export const Performance = getProfilingStory({ - componentName: 'EmailFieldDisplay', + componentName: 'EmailsFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 50, numberOfTestsPerRun: 100, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx deleted file mode 100644 index be1567d863b0..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx +++ /dev/null @@ -1,43 +0,0 @@ -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('company', 'testRawJson', { - key1: 'value1', - key2: 'value2', - }), - ComponentDecorator, - ], - component: JsonFieldDisplay, - args: {}, - parameters: { - chromatic: { disableSnapshot: true }, - }, -}; - -export default meta; - -type Story = StoryObj; - -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, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx index ec9a8291d2c5..0eae95f09db0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx @@ -23,7 +23,7 @@ const meta: Meta = { title: 'UI/Data/Field/Display/MultiSelectFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('company', 'testMultiSelect', [ + getFieldDecorator('company', 'workPolicy', [ 'Option 1', 'Option 2', 'Option 3', diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx similarity index 61% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx index fed4bbe247a3..94f37ee5cfb6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx @@ -1,19 +1,19 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; -import { PhoneFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhoneFieldDisplay'; +import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay'; 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/PhoneFieldDisplay', + title: 'UI/Data/Field/Display/PhonesFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('person', 'phone'), + getFieldDecorator('person', 'phones'), ComponentDecorator, ], - component: PhoneFieldDisplay, + component: PhonesFieldDisplay, args: {}, parameters: { chromatic: { disableSnapshot: true }, @@ -22,7 +22,7 @@ const meta: Meta = { export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; @@ -33,11 +33,17 @@ export const Elipsis: Story = { }; export const WrongNumber: Story = { - decorators: [getFieldDecorator('person', 'phone', 'sdklaskdj')], + decorators: [ + getFieldDecorator('person', 'phones', { + primaryPhoneNumber: '123-456-7890', + primaryPhoneCountryCode: '+1', + additionalPhones: null, + }), + ], }; export const Performance = getProfilingStory({ - componentName: 'PhoneFieldDisplay', + componentName: 'PhonesFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 20, numberOfTestsPerRun: 100, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx index 3c31dc6d5682..3d4b5950aea0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx @@ -10,7 +10,7 @@ const meta: Meta = { title: 'UI/Data/Field/Display/RatingFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('company', 'testRating'), + getFieldDecorator('person', 'performanceRating'), ComponentDecorator, ], component: RatingFieldDisplay, @@ -30,5 +30,5 @@ export const Performance = getProfilingStory({ componentName: 'RatingFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 30, - numberOfTestsPerRun: 50, + numberOfTestsPerRun: 30, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx index 49a076d80a47..989de5e7439f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx @@ -30,7 +30,7 @@ export const Default: Story = {}; export const Performance = getProfilingStory({ componentName: 'RelationFieldDisplay', - averageThresholdInMs: 0.2, + averageThresholdInMs: 0.22, numberOfRuns: 20, numberOfTestsPerRun: 100, }); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingBooleanFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingBooleanFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingBooleanFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingBooleanFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingCurrencyFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingCurrencyFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingCurrencyFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingCurrencyFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingDateFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingDateFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingDateFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingDateFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingFloatFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingFloatFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingFloatFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingFloatFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingStringFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingStringFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingStringFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingStringFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingUUIDFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingUUIDFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingUUIDFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingUUIDFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts similarity index 98% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts index ed0b22070777..1c5e1d2b4f46 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts @@ -1,10 +1,11 @@ import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { Company } from '@/companies/types/Company'; import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; -import { isRecordMatchingFilter } from './isRecordMatchingFilter'; +import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; +import { expect } from '@storybook/test'; const companiesMock = getCompaniesMock(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts new file mode 100644 index 000000000000..6486ca29b92e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts @@ -0,0 +1,1063 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getCompaniesMock } from '~/testing/mock-data/companies'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +const companiesMock = getCompaniesMock(); + +const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +)!; + +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); + +describe('turnObjectDropdownFilterIntoQueryFilter', () => { + it('should work as expected for single filter', () => { + const companyMockNameFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'name', + ); + + const nameFilter: Filter = { + id: 'company-name-filter', + value: companiesMock[0].name, + fieldMetadataId: companyMockNameFieldMetadataId?.id, + displayValue: companiesMock[0].name, + operand: ViewFilterOperand.Contains, + definition: { + type: 'TEXT', + fieldMetadataId: companyMockNameFieldMetadataId?.id, + label: 'Name', + iconName: 'text', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [nameFilter], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + name: { + ilike: '%Linkedin%', + }, + }); + }); + + it('should work as expected for multiple filters', () => { + const companyMockNameFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'name', + ); + + const companyMockEmployeesFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'employees', + ); + + const nameFilter: Filter = { + id: 'company-name-filter', + value: companiesMock[0].name, + fieldMetadataId: companyMockNameFieldMetadataId?.id, + displayValue: companiesMock[0].name, + operand: ViewFilterOperand.Contains, + definition: { + type: 'TEXT', + fieldMetadataId: companyMockNameFieldMetadataId?.id, + label: 'Name', + iconName: 'text', + }, + }; + + const employeesFilter: Filter = { + id: 'company-employees-filter', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.GreaterThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [nameFilter, employeesFilter], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + name: { + ilike: '%Linkedin%', + }, + }, + { + employees: { + gte: 1000, + }, + }, + ], + }); + }); +}); + +describe('should work as expected for the different field types', () => { + it('address field type', () => { + const companyMockAddressFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'address', + ); + + const addressFilterContains: Filter = { + id: 'company-address-filter-contains', + value: '123 Main St', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '123 Main St', + operand: ViewFilterOperand.Contains, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterDoesNotContain: Filter = { + id: 'company-address-filter-does-not-contain', + value: '123 Main St', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '123 Main St', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterIsEmpty: Filter = { + id: 'company-address-filter-is-empty', + value: '', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterIsNotEmpty: Filter = { + id: 'company-address-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + addressFilterContains, + addressFilterDoesNotContain, + addressFilterIsEmpty, + addressFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressStreet2: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressCity: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressState: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressCountry: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressPostcode: { + ilike: '%123 Main St%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + address: { + addressStreet1: { + ilike: '%123 Main St%', + }, + }, + }, + }, + { + not: { + address: { + addressStreet2: { + ilike: '%123 Main St%', + }, + }, + }, + }, + { + not: { + address: { + addressCity: { + ilike: '%123 Main St%', + }, + }, + }, + }, + ], + }, + { + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet1: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressStreet2: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet2: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCity: { + ilike: '', + }, + }, + }, + { + address: { + addressCity: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressState: { + ilike: '', + }, + }, + }, + { + address: { + addressState: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCountry: { + ilike: '', + }, + }, + }, + { + address: { + addressCountry: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressPostcode: { + ilike: '', + }, + }, + }, + { + address: { + addressPostcode: { + is: 'NULL', + }, + }, + }, + ], + }, + ], + }, + { + not: { + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet1: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressStreet2: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet2: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCity: { + ilike: '', + }, + }, + }, + { + address: { + addressCity: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressState: { + ilike: '', + }, + }, + }, + { + address: { + addressState: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCountry: { + ilike: '', + }, + }, + }, + { + address: { + addressCountry: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressPostcode: { + ilike: '', + }, + }, + }, + { + address: { + addressPostcode: { + is: 'NULL', + }, + }, + }, + ], + }, + ], + }, + }, + ], + }); + }); + + it('phones field type', () => { + const personMockPhonesFieldMetadataId = + personMockObjectMetadataItem.fields.find( + (field) => field.name === 'phones', + ); + + const phonesFilterContains: Filter = { + id: 'person-phones-filter-contains', + value: '1234567890', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '1234567890', + operand: ViewFilterOperand.Contains, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterDoesNotContain: Filter = { + id: 'person-phones-filter-does-not-contain', + value: '1234567890', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '1234567890', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterIsEmpty: Filter = { + id: 'person-phones-filter-is-empty', + value: '', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterIsNotEmpty: Filter = { + id: 'person-phones-filter-is-not-empty', + value: '', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + phonesFilterContains, + phonesFilterDoesNotContain, + phonesFilterIsEmpty, + phonesFilterIsNotEmpty, + ], + personMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + ilike: '%1234567890%', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '%1234567890%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + phones: { + primaryPhoneNumber: { + ilike: '%1234567890%', + }, + }, + }, + }, + { + not: { + phones: { + primaryPhoneCountryCode: { + ilike: '%1234567890%', + }, + }, + }, + }, + ], + }, + { + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneNumber: { + ilike: '', + }, + }, + }, + ], + }, + { + or: [ + { + phones: { + primaryPhoneCountryCode: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '', + }, + }, + }, + ], + }, + ], + }, + { + not: { + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneNumber: { + ilike: '', + }, + }, + }, + ], + }, + { + or: [ + { + phones: { + primaryPhoneCountryCode: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '', + }, + }, + }, + ], + }, + ], + }, + }, + ], + }); + }); + + it('emails field type', () => { + const personMockEmailFieldMetadataId = + personMockObjectMetadataItem.fields.find( + (field) => field.name === 'emails', + ); + + const emailsFilterContains: Filter = { + id: 'person-emails-filter-contains', + value: 'test@test.com', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: 'test@test.com', + operand: ViewFilterOperand.Contains, + definition: { + type: 'EMAILS', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + iconName: 'email', + label: 'Emails', + }, + }; + + const emailsFilterDoesNotContain: Filter = { + id: 'person-emails-filter-does-not-contain', + value: 'test@test.com', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: 'test@test.com', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'EMAILS', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + label: 'Emails', + iconName: 'email', + }, + }; + + const emailsFilterIsEmpty: Filter = { + id: 'person-emails-filter-is-empty', + value: '', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'EMAILS', + label: 'Emails', + iconName: 'email', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + }, + }; + + const emailsFilterIsNotEmpty: Filter = { + id: 'person-emails-filter-is-not-empty', + value: '', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'EMAILS', + label: 'Emails', + iconName: 'email', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + emailsFilterContains, + emailsFilterDoesNotContain, + emailsFilterIsEmpty, + emailsFilterIsNotEmpty, + ], + personMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + emails: { + primaryEmail: { + ilike: '%test@test.com%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + emails: { + primaryEmail: { + ilike: '%test@test.com%', + }, + }, + }, + }, + ], + }, + { + or: [ + { + emails: { + primaryEmail: { + ilike: '', + }, + }, + }, + { + emails: { + primaryEmail: { + is: 'NULL', + }, + }, + }, + ], + }, + { + not: { + or: [ + { + emails: { + primaryEmail: { + ilike: '', + }, + }, + }, + { + emails: { + primaryEmail: { + is: 'NULL', + }, + }, + }, + ], + }, + }, + ], + }); + }); + + it('date field type', () => { + const companyMockDateFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'createdAt', + ); + + const dateFilterIsAfter: Filter = { + id: 'company-date-filter-is-after', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.IsAfter, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsBefore: Filter = { + id: 'company-date-filter-is-before', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.IsBefore, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIs: Filter = { + id: 'company-date-filter-is', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.Is, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsEmpty: Filter = { + id: 'company-date-filter-is-empty', + value: '', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsNotEmpty: Filter = { + id: 'company-date-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + dateFilterIsAfter, + dateFilterIsBefore, + dateFilterIs, + dateFilterIsEmpty, + dateFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + createdAt: { + gt: '2024-09-17T20:46:58.922Z', + }, + }, + { + createdAt: { + lt: '2024-09-17T20:46:58.922Z', + }, + }, + { + and: [ + { + createdAt: { + lte: '2024-09-17T23:59:59.999Z', + }, + }, + { + createdAt: { + gte: '2024-09-17T00:00:00.000Z', + }, + }, + ], + }, + { + createdAt: { + is: 'NULL', + }, + }, + { + not: { + createdAt: { + is: 'NULL', + }, + }, + }, + ], + }); + }); + + it('number field type', () => { + const companyMockEmployeesFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'employees', + ); + + const employeesFilterIsGreaterThan: Filter = { + id: 'company-employees-filter-is-greater-than', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.GreaterThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsLessThan: Filter = { + id: 'company-employees-filter-is-less-than', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.LessThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsEmpty: Filter = { + id: 'company-employees-filter-is-empty', + value: '', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsNotEmpty: Filter = { + id: 'company-employees-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + employeesFilterIsGreaterThan, + employeesFilterIsLessThan, + employeesFilterIsEmpty, + employeesFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + employees: { + gte: 1000, + }, + }, + { + employees: { + lte: 1000, + }, + }, + { + employees: { + is: 'NULL', + }, + }, + { + not: { + employees: { + is: 'NULL', + }, + }, + }, + ], + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 14584485370e..323e36e2985a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -10,7 +10,6 @@ import { RecordGqlOperationFilter, RelationFilter, StringFilter, - URLFilter, UUIDFilter, } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; @@ -40,8 +39,6 @@ const applyEmptyFilters = ( switch (filterType) { case 'TEXT': - case 'EMAIL': - case 'PHONE': emptyRecordFilter = { or: [ { [correspondingField.name]: { ilike: '' } as StringFilter }, @@ -86,16 +83,6 @@ const applyEmptyFilters = ( }; break; } - case 'LINK': - emptyRecordFilter = { - or: [ - { [correspondingField.name]: { url: { ilike: '' } } as URLFilter }, - { - [correspondingField.name]: { url: { is: 'NULL' } } as URLFilter, - }, - ], - }; - break; case 'LINKS': { const linksFilters = generateILikeFiltersForCompositeFields( '', @@ -305,8 +292,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } switch (rawUIFilter.definition.type) { - case 'EMAIL': - case 'PHONE': case 'TEXT': switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: @@ -627,43 +612,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ); } break; - case 'LINK': - switch (rawUIFilter.operand) { - case ViewFilterOperand.Contains: - objectRecordFilters.push({ - [correspondingField.name]: { - url: { - ilike: `%${rawUIFilter.value}%`, - }, - } as URLFilter, - }); - break; - case ViewFilterOperand.DoesNotContain: - objectRecordFilters.push({ - not: { - [correspondingField.name]: { - url: { - ilike: `%${rawUIFilter.value}%`, - }, - } as URLFilter, - }, - }); - break; - case ViewFilterOperand.IsEmpty: - case ViewFilterOperand.IsNotEmpty: - applyEmptyFilters( - rawUIFilter.operand, - correspondingField, - objectRecordFilters, - rawUIFilter.definition.type, - ); - break; - default: - throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, - ); - } - break; case 'LINKS': { const linksFilters = generateILikeFiltersForCompositeFields( rawUIFilter.value, diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts index 36947097459e..df178df4c4fd 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts @@ -1,5 +1,6 @@ import { useRecoilValue } from 'recoil'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; @@ -9,6 +10,7 @@ import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hook import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies'; +import { isNull } from '@sniptt/guards'; import { WorkspaceActivationStatus } from '~/generated/graphql'; export const useFindManyParams = ( @@ -43,6 +45,7 @@ export const useLoadRecordIndexTable = (objectNameSingular: string) => { const { setRecordTableData, setIsRecordTableInitialLoading } = useRecordTable(); const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const params = useFindManyParams(objectNameSingular); const recordGqlFields = useRecordTableRecordGqlFields({ objectMetadataItem }); @@ -63,6 +66,7 @@ export const useLoadRecordIndexTable = (objectNameSingular: string) => { onError: () => { setIsRecordTableInitialLoading(false); }, + skip: isNull(currentWorkspaceMember), }); return { diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx index 752deafc8e7f..d670b908cb22 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx @@ -1,18 +1,18 @@ -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { act } from 'react'; import { percentage, sleep, useTableData } from '../useTableData'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState'; import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { ViewType } from '@/views/types/ViewType'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import gql from 'graphql-tag'; -import { BrowserRouter as Router } from 'react-router-dom'; -import { RecoilRoot, useRecoilValue } from 'recoil'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { useRecoilValue } from 'recoil'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const defaultResponseData = { pageInfo: { @@ -23,10 +23,10 @@ const defaultResponseData = { }, totalCount: 1, }; + const mockPerson = { __typename: 'Person', updatedAt: '2021-08-03T19:20:06.000Z', - myCustomObjectId: '123', whatsapp: { primaryPhoneNumber: '+1', primaryPhoneCountryCode: '234-567-890', @@ -41,7 +41,10 @@ const mockPerson = { firstName: 'firstName', lastName: 'lastName', }, - email: 'email', + emails: { + primaryEmail: 'email', + additionalEmails: [], + }, position: 'position', createdBy: { source: 'source', @@ -57,7 +60,7 @@ const mockPerson = { }, performanceRating: 1, createdAt: '2021-08-03T19:20:06.000Z', - phone: { + phones: { primaryPhoneNumber: '+1', primaryPhoneCountryCode: '234-567-890', additionalPhones: [], @@ -66,8 +69,10 @@ const mockPerson = { city: 'city', companyId: '1', intro: 'intro', - workPreference: 'workPrefereance', + deletedAt: null, + workPreference: 'workPreference', }; + const mocks: MockedResponse[] = [ { request: { @@ -86,52 +91,7 @@ const mocks: MockedResponse[] = [ ) { edges { node { - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -167,21 +127,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - -); +const WrapperWithResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const graphqlEmptyResponse = [ { @@ -197,21 +145,9 @@ const graphqlEmptyResponse = [ }, ]; -const WrapperWithEmptyResponse = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - -); +const WrapperWithEmptyResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: graphqlEmptyResponse, +}); describe('useTableData', () => { const recordIndexId = 'people'; @@ -225,6 +161,7 @@ describe('useTableData', () => { useTableData({ recordIndexId, objectNameSingular, + pageSize: 30, callback, delayMs: 0, viewType: ViewType.Kanban, @@ -249,10 +186,11 @@ describe('useTableData', () => { recordIndexId, objectNameSingular, callback, + pageSize: 30, delayMs: 0, }), - { wrapper: Wrapper }, + { wrapper: WrapperWithResponse }, ); await act(async () => { @@ -292,7 +230,7 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); @@ -340,8 +278,10 @@ describe('useTableData', () => { relationObjectMetadataNameSingular: '', relationType: undefined, targetFieldMetadataName: '', + settings: {}, }, position: 7, + settings: {}, showLabel: undefined, size: 100, type: 'DATE_TIME', @@ -379,7 +319,7 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx index 06cb79025bc5..aca53588e1be 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx @@ -9,15 +9,23 @@ import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator' import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; import { getPeopleMock } from '~/testing/mock-data/people'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { RecordDetailRelationSection } from '../RecordDetailRelationSection'; const companiesMock = getCompaniesMock(); const peopleMock = getPeopleMock(); +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + const meta: Meta = { title: 'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection', diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index 9b68416e8192..b866e344842f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -21,7 +21,7 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockPerformance } from './mock'; const RelationFieldValueSetterEffect = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx index e592107c5a1b..cf18193570e2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx @@ -10,7 +10,7 @@ import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fiel import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const draftValue = 'updated Name'; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx index 9eb2e7b3642e..0aeca9fcb3aa 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx @@ -4,7 +4,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const scopeId = 'scopeId'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx index da745581eb75..a7e47f8ba713 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx @@ -1,13 +1,12 @@ import { gql } from '@apollo/client'; -import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { renderHook, waitFor } from '@testing-library/react'; +import { act } from 'react'; +import { useRecoilValue } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useOpenObjectRecordsSpreasheetImportDialog } from '../hooks/useOpenObjectRecordsSpreasheetImportDialog'; const companyId = 'cb2e9f4b-20c3-4759-9315-4ffeecfaf71a'; @@ -26,13 +25,42 @@ const companyMocks = [ ) { createCompanies(data: $data, upsert: $upsert) { __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + accountOwner { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + accountOwnerId + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } } - visaSponsorship address { addressStreet1 addressStreet2 @@ -43,20 +71,31 @@ const companyMocks = [ addressLat addressLng } - position - employees - deletedAt - accountOwnerId annualRecurringRevenue { amountMicros currencyCode } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } } createdAt createdBy { @@ -64,7 +103,36 @@ const companyMocks = [ workspaceMemberId name } - workPolicy + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + idealCustomerProfile introVideo { primaryLinkUrl primaryLinkLabel @@ -75,8 +143,151 @@ const companyMocks = [ primaryLinkLabel secondaryLinks } + name + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + opportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + people { + edges { + node { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + } + } + position tagline - idealCustomerProfile + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } } `, @@ -99,6 +310,9 @@ const companyMocks = [ createCompanies: [ { id: companyId, + favorites: { + edges: [], + }, }, ], }, @@ -112,17 +326,9 @@ const fakeCsv = () => { return new File([blob], 'fakeData.csv', { type: 'text/csv' }); }; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: companyMocks, +}); // TODO: improve object metadata item seeds to have more field types to add tests on composite fields here describe('useSpreadsheetCompanyImport', () => { diff --git a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx index 83fa74e7b864..263b70decf0f 100644 --- a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx +++ b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx @@ -8,7 +8,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { query, responseData, diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx index 5ef756baf4c6..a4e9f5294e83 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx @@ -3,12 +3,18 @@ import { ComponentDecorator } from 'twenty-ui'; import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails'; import { CalendarChannelVisibility } from '~/generated/graphql'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelDetails', component: SettingsAccountsCalendarChannelDetails, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { calendarChannel: { id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a', diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx index 49f279316908..83c05df92b98 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx @@ -2,12 +2,18 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelsGeneral', component: SettingsAccountsCalendarChannelsGeneral, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], }; export default meta; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx index 02264d8e66fe..a951150c94f3 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx @@ -4,12 +4,18 @@ import { ComponentDecorator } from 'twenty-ui'; import { MessageChannelContactAutoCreationPolicy } from '@/accounts/types/MessageChannel'; import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails'; import { MessageChannelVisibility } from '~/generated/graphql'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/MessageChannels/SettingsAccountsMessageChannelDetails', component: SettingsAccountsMessageChannelDetails, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { messageChannel: { id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a', diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx index 7499adea0338..58f33bf2210f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx @@ -3,7 +3,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldDescriptionForm } from '../SettingsDataModelFieldDescriptionForm'; const meta: Meta = { @@ -25,11 +25,15 @@ type Story = StoryObj; export const Default: Story = {}; +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', +); + export const WithFieldMetadataItem: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ description }) => description === 'description', - )!, + ), }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx index 150efdc46683..555db1bc92d1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx @@ -5,7 +5,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldIconLabelForm } from '../SettingsDataModelFieldIconLabelForm'; const StyledContainer = styled.div` @@ -32,11 +32,15 @@ type Story = StoryObj; export const Default: Story = {}; +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', +); + export const WithFieldMetadataItem: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'name', - )!, + ), }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx index aff46aa2da0e..09aaf60c282c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx @@ -7,10 +7,18 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldSettingsFormCard } from '../SettingsDataModelFieldSettingsFormCard'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( ({ type }) => type === FieldMetadataType.Text, )!; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx index 3df8c6e7664a..6b0fb477eef2 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx @@ -6,14 +6,23 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { - mockedCompanyObjectMetadataItem, - mockedOpportunityObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldPreviewCard } from '../SettingsDataModelFieldPreviewCard'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + const meta: Meta = { title: 'Modules/Settings/DataModel/Fields/Preview/SettingsDataModelFieldPreviewCard', @@ -38,7 +47,7 @@ type Story = StoryObj; export const LabelIdentifier: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name, type }) => name === 'name' && type === FieldMetadataType.FullName, ), @@ -47,7 +56,7 @@ export const LabelIdentifier: Story = { export const Text: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name, type }) => name === 'city' && type === FieldMetadataType.Text, ), }, @@ -55,7 +64,7 @@ export const Text: Story = { export const Boolean: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'idealCustomerProfile' && type === FieldMetadataType.Boolean, ), @@ -65,7 +74,7 @@ export const Boolean: Story = { export const Currency: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'annualRecurringRevenue' && type === FieldMetadataType.Currency, @@ -76,7 +85,7 @@ export const Currency: Story = { export const Date: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type === FieldMetadataType.DateTime, ), objectMetadataItem: mockedCompanyObjectMetadataItem, @@ -85,7 +94,7 @@ export const Date: Story = { export const Links: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'linkedinLink' && type === FieldMetadataType.Links, ), @@ -95,7 +104,7 @@ export const Links: Story = { export const Number: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type === FieldMetadataType.Number, ), objectMetadataItem: mockedCompanyObjectMetadataItem, @@ -114,7 +123,7 @@ export const Rating: Story = { export const Relation: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'company', ), relationObjectMetadataItem: mockedCompanyObjectMetadataItem, @@ -123,7 +132,7 @@ export const Relation: Story = { export const Select: Story = { args: { - fieldMetadataItem: mockedOpportunityObjectMetadataItem.fields.find( + fieldMetadataItem: mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === 'stage' && type === FieldMetadataType.Select, ), objectMetadataItem: mockedOpportunityObjectMetadataItem, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx index a3fbadbf1a24..c9c6b0d4367b 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx @@ -1,35 +1,34 @@ -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 { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useFieldPreviewValue } from '../useFieldPreviewValue'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', ); +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFieldPreviewValue', () => { it('returns null if skip is true', () => { // Given const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); @@ -52,7 +51,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for a Currency field", () => { // Given const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); @@ -106,7 +105,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for a Select field", () => { // Given const fieldName = 'stage'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, ); @@ -169,7 +168,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for other field types", () => { // Given const fieldName = 'employees'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts index a8d9b9a4af7e..8eeda74fbb79 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts @@ -1,16 +1,22 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedOpportunityObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getCurrencyFieldPreviewValue } from '../getCurrencyFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getCurrencyFieldPreviewValue', () => { it('returns null if the field is not a Currency field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.Currency, ); @@ -26,7 +32,7 @@ describe('getCurrencyFieldPreviewValue', () => { }); const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts index 7acf2b0cfe4c..c3a649dbad77 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts @@ -1,17 +1,21 @@ 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'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); describe('getFieldPreviewValue', () => { it("returns the field's defaultValue from metadata if it exists", () => { // Given const fieldName = 'idealCustomerProfile'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); @@ -29,7 +33,7 @@ describe('getFieldPreviewValue', () => { it('returns a placeholder defaultValue if the field metadata does not have a defaultValue', () => { // Given const fieldName = 'employees'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); @@ -50,25 +54,7 @@ describe('getFieldPreviewValue', () => { 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( + const fieldMetadataItem = mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts index 824d6e820167..2737a829ee91 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts @@ -1,15 +1,20 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedCustomObjectMetadataItem, -} from '~/testing/mock-data/metadata'; - +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getMultiSelectFieldPreviewValue } from '../getMultiSelectFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getMultiSelectFieldPreviewValue', () => { it('returns null if the field is not a Multi-Select field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.MultiSelect, ); @@ -24,10 +29,11 @@ describe('getMultiSelectFieldPreviewValue', () => { expect(previewValue).toBeNull(); }); - const fieldName = 'priority'; - const selectFieldMetadataItem = mockedCustomObjectMetadataItem.fields.find( - ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, - ); + const fieldName = 'stage'; + const selectFieldMetadataItem = + mockedOpportunityObjectMetadataItem?.fields.find( + ({ name }) => name === fieldName, + ); if (!selectFieldMetadataItem) { throw new Error(`Field '${fieldName}' not found`); @@ -52,7 +58,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['MEDIUM', 'LOW']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); }); it("returns all option values if no defaultValue was found in the field's metadata", () => { @@ -69,7 +81,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); expect(previewValue).toEqual( fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value), ); @@ -89,7 +107,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); expect(previewValue).toEqual( fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value), ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts index 3572d58d9007..109feefb3ab1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts @@ -1,15 +1,21 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedCustomObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getSelectFieldPreviewValue } from '../getSelectFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getSelectFieldPreviewValue', () => { it('returns null if the field is not a Select field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.Select, ); @@ -24,9 +30,9 @@ describe('getSelectFieldPreviewValue', () => { expect(previewValue).toBeNull(); }); - const fieldName = 'priority'; - const fieldMetadataItem = mockedCustomObjectMetadataItem.fields.find( - ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, + const fieldName = 'stage'; + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( + ({ name }) => name === fieldName, ); if (!fieldMetadataItem) { @@ -35,7 +41,7 @@ describe('getSelectFieldPreviewValue', () => { it("returns the defaultValue as an option value if a valid defaultValue is found in the field's metadata", () => { // Given - const defaultValue = "'MEDIUM'"; + const defaultValue = "'NEW'"; const fieldMetadataItemWithDefaultValue = { ...fieldMetadataItem, defaultValue, @@ -47,7 +53,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('MEDIUM'); + expect(previewValue).toBe('NEW'); }); it("returns the first option value if no defaultValue was found in the field's metadata", () => { @@ -64,7 +70,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('LOW'); + expect(previewValue).toBe('NEW'); expect(previewValue).toBe( fieldMetadataItemWithDefaultValue.options?.[0]?.value, ); @@ -84,7 +90,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('LOW'); + expect(previewValue).toBe('NEW'); expect(previewValue).toBe( fieldMetadataItemWithDefaultValue.options?.[0]?.value, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx index 4345f59229e9..173106174fdf 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx @@ -4,9 +4,12 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelObjectAboutForm } from '../SettingsDataModelObjectAboutForm'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); const StyledContainer = styled.div` flex: 1; diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts b/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts index 95b0aac275e1..750604ef1515 100644 --- a/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts @@ -1,10 +1,14 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { - mockedCompanyObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getFieldPreviewValueFromRecord } from '../getFieldPreviewValueFromRecord'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); describe('getFieldPreviewValueFromRecord', () => { describe('RELATION field', () => { @@ -21,9 +25,13 @@ describe('getFieldPreviewValueFromRecord', () => { }, __typename: 'Opportunity', }; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === 'people', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ @@ -43,9 +51,13 @@ describe('getFieldPreviewValueFromRecord', () => { company: relationRecord, __typename: 'Opportunity', }; - const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'company', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ @@ -62,9 +74,13 @@ describe('getFieldPreviewValueFromRecord', () => { it('returns the record field value', () => { // Given const record = { id: '', name: 'Twenty', __typename: 'Opportunity' }; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === 'name', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts index e0799f13ac2e..36de554158df 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts +++ b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts @@ -1,5 +1,5 @@ -import { renderHook } from '@testing-library/react'; import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; +import { renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; jest.mock( @@ -44,6 +44,6 @@ describe('useServerlessFunctionUpdateFormState', () => { const { formValues } = result.current; - expect(formValues).toEqual({ name: '', description: '', code: '' }); + expect(formValues).toEqual({ name: '', description: '', code: undefined }); }); }); diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts index 367710df7137..9e8a13483810 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts +++ b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts @@ -1,6 +1,6 @@ -import { Dispatch, SetStateAction, useState } from 'react'; import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunction'; import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode'; +import { Dispatch, SetStateAction, useState } from 'react'; import { FindOneServerlessFunctionSourceCodeQuery } from '~/generated-metadata/graphql'; export type ServerlessFunctionNewFormValues = { diff --git a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx index 5f1b8c02129d..31d04e0c1edb 100644 --- a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx +++ b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx @@ -11,7 +11,6 @@ import { graphqlMocks } from '~/testing/graphqlMocks'; import { getPeopleMock, peopleQueryResult } from '~/testing/mock-data/people'; import { mockedWorkspaceMemberData } from '~/testing/mock-data/users'; -import { viewQueryResultMock } from '~/testing/mock-data/views'; import { RecordShowPage } from '../RecordShowPage'; const peopleMock = getPeopleMock(); @@ -63,13 +62,6 @@ const meta: Meta = { }, }); }), - graphql.query('FindManyViews', () => { - return HttpResponse.json({ - data: { - views: viewQueryResultMock, - }, - }); - }), graphqlMocks.handlers, ], }, diff --git a/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx b/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx index 1aa66449abcc..2b7ab8f8e528 100644 --- a/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx +++ b/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx @@ -38,6 +38,24 @@ export const Default: Story = { }, }; +export const DateTimeSettingsTimeFormat: Story = { + play: async () => { + const canvas = within(document.body); + + await canvas.findByText('Date and time'); + + const timeFormatSelect = await canvas.findByText('24h (08:33)'); + + userEvent.click(timeFormatSelect); + + const timeFormatOptions = await canvas.findByText('12h (8:33 AM)'); + + userEvent.click(timeFormatOptions); + + await canvas.findByText('12h (8:33 AM)'); + }, +}; + export const DateTimeSettingsTimezone: Story = { play: async () => { const canvas = within(document.body); @@ -77,21 +95,3 @@ export const DateTimeSettingsDateFormat: Story = { await canvas.findByText('Jun 13, 2022'); }, }; - -export const DateTimeSettingsTimeFormat: Story = { - play: async () => { - const canvas = within(document.body); - - await canvas.findByText('Date and time'); - - const timeFormatSelect = await canvas.findByText('24h (08:33)'); - - userEvent.click(timeFormatSelect); - - const timeFormatOptions = await canvas.findByText('12h (8:33 AM)'); - - userEvent.click(timeFormatOptions); - - await canvas.findByText('12h (8:33 AM)'); - }, -}; diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx index e828bfa766e8..fc7cecd82dee 100644 --- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx @@ -1,3 +1,4 @@ +import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; @@ -33,6 +34,6 @@ export const Default: Story = { const canvas = within(canvasElement); sleep(1000); - await canvas.findByText('PostgreSQL database'); + expect(await canvas.findByText('PostgreSQL database')).toBeInTheDocument(); }, }; diff --git a/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx b/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx index 283e7046e0ab..c107d5466387 100644 --- a/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { PreComputedChipGeneratorsContext } from '@/object-metadata/context/PreComputedChipGeneratorsContext'; import { getRecordChipGenerators } from '@/object-record/utils/getRecordChipGenerators'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const ChipGeneratorsDecorator: Decorator = (Story) => { const { chipGeneratorPerObjectPerField, identifierChipGeneratorPerObject } = diff --git a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx index 7125e4326dc6..244772c809e6 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -12,7 +12,7 @@ import { import { RecoilRoot } from 'recoil'; import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; -import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider'; +import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { UserProviderEffect } from '@/users/components/UserProviderEffect'; import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider'; @@ -21,6 +21,7 @@ import { UserProvider } from '~/modules/users/components/UserProvider'; import { mockedApolloClient } from '~/testing/mockedApolloClient'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; +import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; import { IconsProvider } from 'twenty-ui'; import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout'; @@ -64,29 +65,33 @@ const ApolloStorybookDevLogEffect = () => { const Providers = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx index 9c2633037b5b..5808791d1ae2 100644 --- a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx @@ -2,7 +2,7 @@ import { ApolloProvider } from '@apollo/client'; import { Decorator } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider'; +import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider'; import { InitializeHotkeyStorybookHookEffect } from '../InitializeHotkeyStorybookHook'; import { mockedApolloClient } from '../mockedApolloClient'; diff --git a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx index 62cee6f5077b..e61885ed9a0d 100644 --- a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx @@ -12,7 +12,7 @@ import { import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getPeopleMock } from '~/testing/mock-data/people'; import { mockedTasks } from '~/testing/mock-data/tasks'; import { isDefined } from '~/utils/isDefined'; @@ -56,7 +56,7 @@ const RecordMockSetterEffect = ({ export const getFieldDecorator = ( - objectNameSingular: 'company' | 'person' | 'task', + objectNameSingular: 'company' | 'person' | 'task' | 'workflowVersions', fieldName: string, fieldValue?: any, ): Decorator => diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index c5d319b460b7..a69d6c17c660 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -11,7 +11,6 @@ import { getCompanyDuplicateMock, } from '~/testing/mock-data/companies'; import { mockedClientConfig } from '~/testing/mock-data/config'; -import { mockedObjectMetadataItemsQueryResult } from '~/testing/mock-data/metadata'; import { mockedNotes } from '~/testing/mock-data/notes'; import { getPeopleMock } from '~/testing/mock-data/people'; import { mockedRemoteTables } from '~/testing/mock-data/remote-tables'; @@ -19,6 +18,7 @@ import { mockedUserData } from '~/testing/mock-data/users'; import { mockedViewsData } from '~/testing/mock-data/views'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; +import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result'; import { mockedTasks } from '~/testing/mock-data/tasks'; import { mockedRemoteServers } from './mock-data/remote-servers'; import { mockedViewFieldsData } from './mock-data/view-fields'; @@ -58,7 +58,7 @@ export const graphqlMocks = { getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? '', () => { return HttpResponse.json({ - data: mockedObjectMetadataItemsQueryResult, + data: mockedStandardObjectMetadataQueryResult, }); }, ), @@ -297,7 +297,7 @@ export const graphqlMocks = { graphql.query('FindManyTasks', () => { return HttpResponse.json({ data: { - activities: { + tasks: { edges: mockedTasks.map(({ taskTargets, ...rest }) => ({ node: { ...rest, @@ -320,6 +320,26 @@ export const graphqlMocks = { }, }); }), + graphql.query('FindManyTaskTargets', () => { + return HttpResponse.json({ + data: { + taskTargets: { + edges: mockedTasks.flatMap((task) => + task.taskTargets.map((target) => ({ + node: target, + cursor: null, + })), + ), + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: null, + endCursor: null, + }, + }, + }, + }); + }), graphql.query('FindManyFavorites', () => { return HttpResponse.json({ data: { diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index abd11cab1832..98e83cb833a5 100644 --- a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx @@ -2,7 +2,7 @@ import { ReactNode, useEffect, useState } from 'react'; import { useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const JestObjectMetadataItemSetter = ({ children, diff --git a/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts b/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts new file mode 100644 index 000000000000..f27e4f3a3cd8 --- /dev/null +++ b/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts @@ -0,0 +1,37 @@ +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; +import { prefillRecord } from '@/object-record/utils/prefillRecord'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +export const generateEmptyJestRecordNode = ({ + objectNameSingular, + input, + withDepthOneRelation = false, +}: { + objectNameSingular: string; + input: Record; + withDepthOneRelation?: boolean; +}) => { + const objectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === objectNameSingular, + ); + + if (!objectMetadataItem) { + throw new Error( + `ObjectMetadataItem not found for objectNameSingular: ${objectNameSingular} while generating empty Jest record node`, + ); + } + + const prefilledRecord = prefillRecord({ objectMetadataItem, input }); + + return getRecordNodeFromRecord({ + record: prefilledRecord, + objectMetadataItem, + objectMetadataItems: generatedMockObjectMetadataItems, + recordGqlFields: withDepthOneRelation + ? generateDepthOneRecordGqlFields({ + objectMetadataItem, + }) + : undefined, + }); +}; diff --git a/packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx similarity index 94% rename from packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx rename to packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index 3c8f21553323..ea0e6528f0bc 100644 --- a/packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -5,7 +5,7 @@ import { MutableSnapshot, RecoilRoot } from 'recoil'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -export const getJestHookWrapper = ({ +export const getJestMetadataAndApolloMocksWrapper = ({ apolloMocks, onInitializeRecoilSnapshot, }: { diff --git a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts index c30b74ec2344..6832293415c8 100644 --- a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts +++ b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts @@ -51,6 +51,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed0dfa31-8e2f-4b23-87e4-8fa55eb16729', type: 'TEXT', name: 'operation', @@ -72,6 +73,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '814795ef-6f2d-4798-a6f9-4e1c87c68d43', type: 'DATE_TIME', name: 'deletedAt', @@ -93,6 +95,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1c802ccf-c0ae-4b04-8c1e-f77417e6c3f8', type: 'DATE_TIME', name: 'updatedAt', @@ -114,6 +117,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f210155-9cdc-48c6-9803-e20f63512024', type: 'DATE_TIME', name: 'createdAt', @@ -135,6 +139,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5fa54f64-3363-4a21-89ca-30d4816d8c77', type: 'UUID', name: 'id', @@ -156,6 +161,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8876650-a7b6-4a9f-95b4-9ec1d6c232cc', type: 'TEXT', name: 'targetUrl', @@ -177,6 +183,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'afae3f60-bfeb-4faf-a899-b0eb0fefac51', type: 'TEXT', name: 'description', @@ -233,6 +240,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ad82920a-857a-4357-8e4a-ed70961ba5d8', type: 'DATE_TIME', name: 'updatedAt', @@ -254,6 +262,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '89662f00-b57c-49f6-aa48-d1c84f5fd7c7', type: 'UUID', name: 'personId', @@ -275,6 +284,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '19270b1d-73b4-4aa9-8106-c1c81351ec53', type: 'UUID', name: 'rocketId', @@ -296,6 +306,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c796ac5-0592-455f-9a0a-66ad53c6e4cf', type: 'UUID', name: 'companyId', @@ -317,6 +328,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dadac630-f64e-4d3d-9923-78ca579373f3', type: 'RELATION', name: 'rocket', @@ -364,6 +376,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0fcdebbb-a332-45ec-ab46-c30d5d7f9ef0', type: 'DATE_TIME', name: 'createdAt', @@ -385,6 +398,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37882bc2-c8d8-4cc5-bc13-4b820cc05b83', type: 'UUID', name: 'taskId', @@ -406,6 +420,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da63ddb4-7c19-49f0-bf90-ac2cc9486ae7', type: 'DATE_TIME', name: 'deletedAt', @@ -427,6 +442,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26cc6ba3-cff7-4b84-bf78-71823187a824', type: 'RELATION', name: 'person', @@ -474,6 +490,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4414119-6f9c-465a-9ee2-95d1fc5eec01', type: 'UUID', name: 'opportunityId', @@ -495,6 +512,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '598688a1-9766-439d-abd3-c0a47c8f36a3', type: 'RELATION', name: 'opportunity', @@ -542,6 +560,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd7f57e8-a67f-4be9-a971-b5609cb0fb83', type: 'RELATION', name: 'company', @@ -589,6 +608,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '400c9e53-283b-42d8-a69f-5010fb75d977', type: 'UUID', name: 'id', @@ -610,6 +630,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ec532542-a4dc-4722-99c3-fca6366db597', type: 'RELATION', name: 'task', @@ -692,6 +713,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26c57af1-5c70-4a9d-974f-e54c6a77a2b4', type: 'RELATION', name: 'rocket', @@ -739,6 +761,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '434d1fd2-e6e0-4de7-9b15-706398e34d2d', type: 'DATE_TIME', name: 'deletedAt', @@ -760,6 +783,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '06fcb5e2-b2f7-4118-a9f0-34558429b72c', type: 'DATE_TIME', name: 'updatedAt', @@ -781,6 +805,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4b6aaf36-4247-4bbb-b26d-64987b02f805', type: 'RELATION', name: 'company', @@ -828,6 +853,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c9bbd140-d9ab-4557-bd77-b446cd80774b', type: 'UUID', name: 'personId', @@ -849,6 +875,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b86fd021-b648-4a1e-b02e-080f0b280303', type: 'UUID', name: 'rocketId', @@ -870,6 +897,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd3a9c3d-a29e-4b27-9fdb-0e8959f21f10', type: 'UUID', name: 'opportunityId', @@ -891,6 +919,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e6a5a8b1-ebef-4e01-ba22-a5f86d894eb5', type: 'UUID', name: 'id', @@ -912,6 +941,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9d568132-6cbc-4e87-95e8-7c2509549391', type: 'RELATION', name: 'note', @@ -959,6 +989,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f80afcd-8ed7-4bf9-a987-9de3d1cddc81', type: 'RELATION', name: 'opportunity', @@ -1006,6 +1037,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc63ee78-06ac-4312-b223-1d41a7ea2af4', type: 'UUID', name: 'noteId', @@ -1027,6 +1059,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '646f3b4b-0fad-495a-a90d-136593464c7f', type: 'UUID', name: 'companyId', @@ -1048,6 +1081,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2b9481a9-c605-45d0-8aad-801a19c4b92c', type: 'DATE_TIME', name: 'createdAt', @@ -1069,6 +1103,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77508442-f0de-4809-b690-3c998edfc0b5', type: 'RELATION', name: 'person', @@ -1151,6 +1186,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '27e79204-ba19-4791-8110-ec2bdc523e07', type: 'TEXT', name: 'messageThreadExternalId', @@ -1172,6 +1208,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d9f0c85-c16f-41e9-9241-2acd90781cdd', type: 'RELATION', name: 'message', @@ -1219,6 +1256,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '72a85b74-9803-4279-9f74-dafb833847fb', type: 'DATE_TIME', name: 'createdAt', @@ -1240,6 +1278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6cb121b9-32d1-44fe-af26-324d73ffe0ac', type: 'DATE_TIME', name: 'deletedAt', @@ -1261,6 +1300,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9491ddd2-aea5-47fe-bd93-09fb6969b20c', type: 'TEXT', name: 'messageExternalId', @@ -1282,6 +1322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f3eafb28-947a-4c7a-9464-24fa5549fb03', type: 'UUID', name: 'id', @@ -1303,6 +1344,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dbfff493-11a0-4866-9ef6-e4e8418a661a', type: 'UUID', name: 'messageChannelId', @@ -1324,6 +1366,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '95488a9d-6473-47e8-aa62-a04d49238a2f', type: 'UUID', name: 'messageId', @@ -1345,6 +1388,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f4582d66-df12-4499-8ede-ab347427241b', type: 'RELATION', name: 'messageChannel', @@ -1392,6 +1436,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7bf79a5a-f5b7-495e-a336-4ddf85a5b2f7', type: 'SELECT', name: 'direction', @@ -1428,6 +1473,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a18edc4a-1b58-4a78-8de4-9564479b09cc', type: 'DATE_TIME', name: 'updatedAt', @@ -1484,6 +1530,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ea62fcb2-2161-47db-9151-19011419ac66', type: 'DATE_TIME', name: 'createdAt', @@ -1505,6 +1552,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2f2b624-93bf-4d16-8517-bf66e43cabc4', type: 'RELATION', name: 'message', @@ -1552,6 +1600,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b92e3ff7-e5e0-4fec-b36e-cb496e2b57ae', type: 'UUID', name: 'id', @@ -1573,6 +1622,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef5a67e6-88dd-4c92-a9be-7ab0605804e7', type: 'DATE_TIME', name: 'deletedAt', @@ -1594,6 +1644,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f3db188f-7329-40bc-9978-e30e5c07d962', type: 'RELATION', name: 'person', @@ -1641,6 +1692,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26c81156-529d-4bec-b5fb-f92e991907b5', type: 'UUID', name: 'messageId', @@ -1662,6 +1714,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa502069-91c1-4eeb-bc8e-b22240564fe1', type: 'TEXT', name: 'displayName', @@ -1683,6 +1736,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd205df4f-f9de-4f61-9a85-40f340a4de23', type: 'RELATION', name: 'workspaceMember', @@ -1730,6 +1784,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0cf35f0a-eb64-47ef-88a4-55b92ca57c64', type: 'UUID', name: 'personId', @@ -1751,6 +1806,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e8550477-6034-45f4-8370-5a1cd75f7a55', type: 'UUID', name: 'workspaceMemberId', @@ -1772,6 +1828,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8dcde658-c38a-4659-8546-89c60465d36e', type: 'TEXT', name: 'handle', @@ -1793,6 +1850,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '56ff0ed7-7916-4610-834c-a7c0657fa9e7', type: 'DATE_TIME', name: 'updatedAt', @@ -1814,6 +1872,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7a8597e3-bbce-4ff5-9f6e-9bf3c7fd43fa', type: 'SELECT', name: 'role', @@ -1899,6 +1958,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9061081a-eed9-49b8-99ab-9a7b8ce7a355', type: 'BOOLEAN', name: 'isContactAutoCreationEnabled', @@ -1920,6 +1980,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'df104a7c-39e5-491d-8bde-6e3f75a3156b', type: 'DATE_TIME', name: 'syncedAt', @@ -1941,6 +2002,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '751ef307-be5e-4451-9434-e2bae8861873', type: 'BOOLEAN', name: 'excludeNonProfessionalEmails', @@ -1962,6 +2024,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9a8520ed-6577-46b0-a732-1a08aafb0160', type: 'SELECT', name: 'visibility', @@ -2005,6 +2068,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f7f7ea2-c5d0-47bc-bb0e-e3afc6d82b91', type: 'DATE_TIME', name: 'updatedAt', @@ -2026,6 +2090,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dafc931d-0466-4c83-91b3-72be3fdee12f', type: 'DATE_TIME', name: 'createdAt', @@ -2047,6 +2112,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '97ba10a8-da33-4d1f-a9c6-964f814f5fd7', type: 'DATE_TIME', name: 'deletedAt', @@ -2068,6 +2134,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a3c22a32-9c6c-4294-abc4-7b3f9b6d8816', type: 'RELATION', name: 'messageChannelMessageAssociations', @@ -2115,6 +2182,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c1686e5a-1af6-45d9-a9f8-d2ecaef71526', type: 'RELATION', name: 'connectedAccount', @@ -2162,6 +2230,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7eaa0427-04b4-4cf8-9676-4e28c88a0fa8', type: 'DATE_TIME', name: 'syncStageStartedAt', @@ -2183,6 +2252,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '290318a3-e994-4dee-8dd1-6bebe92043af', type: 'SELECT', name: 'syncStatus', @@ -2240,6 +2310,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7238d6c1-c54c-4787-aa4f-79294220acdc', type: 'BOOLEAN', name: 'isSyncEnabled', @@ -2261,6 +2332,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9416f68a-84cb-4b1f-805b-e58fb009cc44', type: 'TEXT', name: 'syncCursor', @@ -2282,6 +2354,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '09ec080b-c8fe-432f-aff1-c151861c3ec7', type: 'SELECT', name: 'contactAutoCreationPolicy', @@ -2326,6 +2399,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd30ddf0c-769a-4480-a9cf-fea5867f9eea', type: 'BOOLEAN', name: 'excludeGroupEmails', @@ -2347,6 +2421,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '839a7a90-3b1a-4f76-8207-5a0779ca909d', type: 'SELECT', name: 'type', @@ -2383,6 +2458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b0026844-d54e-4fe2-af36-ed7ac7be1833', type: 'UUID', name: 'connectedAccountId', @@ -2404,6 +2480,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ca7d194-3cdc-4ff9-a90f-6bfaedba8280', type: 'TEXT', name: 'handle', @@ -2425,6 +2502,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f87d969-7dae-4a25-8b11-9577aa11285e', type: 'UUID', name: 'id', @@ -2446,6 +2524,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aafcc52e-d761-4b6a-97a6-1bc097e2ccd2', type: 'SELECT', name: 'syncStage', @@ -2510,6 +2589,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33180dbd-4124-43eb-84c3-17e32b4848e4', type: 'NUMBER', name: 'throttleFailureCount', @@ -2566,6 +2646,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8ca32bd7-4999-4562-9e41-698a458944ea', type: 'DATE_TIME', name: 'deletedAt', @@ -2587,6 +2668,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '236195db-49d1-4386-99b9-4518ab7586f2', type: 'RELATION', name: 'accountOwner', @@ -2634,6 +2716,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '919dadd4-8dda-479f-a8a7-b4ed89eafae5', type: 'RELATION', name: 'calendarChannels', @@ -2681,6 +2764,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '75c78041-39ca-4b46-bcff-6ed0af05248e', type: 'TEXT', name: 'handleAliases', @@ -2702,6 +2786,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd3f821eb-c8b1-48a5-aebb-1bfe23c4128e', type: 'DATE_TIME', name: 'updatedAt', @@ -2723,6 +2808,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f74d250-76f0-4eff-8e27-d1a12782517d', type: 'TEXT', name: 'lastSyncHistoryId', @@ -2744,6 +2830,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fd6c04f4-8dba-47ac-a597-90300eb1a079', type: 'UUID', name: 'id', @@ -2765,6 +2852,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8f7d7528-83a8-442e-b0b6-958f52a1de5e', type: 'DATE_TIME', name: 'authFailedAt', @@ -2786,6 +2874,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '762d59c0-f7e9-410f-b5dc-df66d764de0d', type: 'TEXT', name: 'handle', @@ -2808,6 +2897,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f45f5e44-f88a-490c-b112-df2465b612a3', type: 'TEXT', name: 'refreshToken', @@ -2829,6 +2919,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2aa282f3-5542-47e8-adf3-4384e9ce5d10', type: 'UUID', name: 'accountOwnerId', @@ -2850,6 +2941,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f411e82-b626-4456-896c-d5a326e5e02a', type: 'TEXT', name: 'accessToken', @@ -2871,6 +2963,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c21734fd-6539-410f-bc1a-e91a3177b9c9', type: 'DATE_TIME', name: 'createdAt', @@ -2892,6 +2985,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5b0a8a38-9d02-4e06-8f07-2f76f0ab70eb', type: 'TEXT', name: 'provider', @@ -2913,6 +3007,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '178f1e8a-cbbf-448e-95f3-d1262d9ff33d', type: 'RELATION', name: 'messageChannels', @@ -2995,6 +3090,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e7b7d2f-02fb-426b-8e3e-392225f5b6b3', type: 'RELATION', name: 'taskTargets', @@ -3042,6 +3138,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78f1d502-397e-4cce-b096-a525b2d373e2', type: 'RELATION', name: 'noteTargets', @@ -3089,6 +3186,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c692555c-020e-46a4-b537-c9c4c7d3cd32', type: 'ACTOR', name: 'createdBy', @@ -3113,6 +3211,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6d32aa8b-28c0-4e35-b45e-9643fd8e1c33', type: 'CURRENCY', name: 'amount', @@ -3137,6 +3236,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c859b99c-2554-483d-8ffe-4d29cb9c8459', type: 'RELATION', name: 'company', @@ -3184,6 +3284,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ad6853d-d1e9-46a8-a77a-38eeae27e1d6', type: 'RELATION', name: 'pointOfContact', @@ -3231,6 +3332,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f6c45230-619b-4432-82fd-e5bed0c4e8f4', type: 'SELECT', name: 'stage', @@ -3288,6 +3390,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5d3ca5b4-9468-4061-8c6b-ef03a9b123df', type: 'RELATION', name: 'favorites', @@ -3335,6 +3438,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3bc37e42-10f2-4501-94e4-760a7c3fa38e', type: 'POSITION', name: 'position', @@ -3356,6 +3460,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9019bf90-efbc-4499-a87b-0624bda5a559', type: 'RELATION', name: 'timelineActivities', @@ -3404,6 +3509,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c7472787-44e9-4641-9901-cc4909ca031d', type: 'DATE_TIME', name: 'closeDate', @@ -3425,6 +3531,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '01e75534-627f-4a49-bc28-c08170a71085', type: 'UUID', name: 'id', @@ -3446,6 +3553,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6d5401c0-d456-42c4-85d4-9666900615ef', type: 'TEXT', name: 'name', @@ -3467,6 +3575,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa651efa-1576-4edc-9599-070666a76dda', type: 'RELATION', name: 'attachments', @@ -3514,6 +3623,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b8eb99bd-3485-44bb-8483-f0c600af4e92', type: 'UUID', name: 'pointOfContactId', @@ -3535,6 +3645,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '55fd22aa-80df-4c0b-b5ee-f19163d10a82', type: 'UUID', name: 'companyId', @@ -3556,6 +3667,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd487fe61-e181-4077-ba4e-9c7b466085ad', type: 'DATE_TIME', name: 'updatedAt', @@ -3577,6 +3689,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bf11e668-6598-42ee-8c81-563641403ba9', type: 'DATE_TIME', name: 'deletedAt', @@ -3598,6 +3711,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2e77023d-fdeb-454d-9368-ab638a68a0bb', type: 'DATE_TIME', name: 'createdAt', @@ -3619,6 +3733,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ac708d8f-abd4-436f-aec7-9a8c4ec2cd28', type: 'RELATION', name: 'activityTargets', @@ -3701,6 +3816,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4a2da98e-6880-47be-8673-165e1d77a910', type: 'RELATION', name: 'rocket', @@ -3748,6 +3864,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '03aab98d-e16c-48fb-91e7-bffc2300402d', type: 'UUID', name: 'opportunityId', @@ -3769,6 +3886,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e1d06322-0658-4c45-9c9b-8a42750c8751', type: 'RELATION', name: 'company', @@ -3816,6 +3934,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ca812263-7b62-4e7f-8f31-bed3de2d4a94', type: 'DATE_TIME', name: 'updatedAt', @@ -3837,6 +3956,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '984d38ae-335d-41fb-ac29-9029ff43c4c6', type: 'UUID', name: 'companyId', @@ -3858,6 +3978,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2531e1e-39ad-46c8-af56-853fe1cc7dd6', type: 'RELATION', name: 'activity', @@ -3905,6 +4026,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26d681d7-4ef2-40eb-bd0c-7e0b7d6d6cb0', type: 'UUID', name: 'personId', @@ -3926,6 +4048,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc9c8fec-55d2-40b6-9fcd-d441024be60f', type: 'DATE_TIME', name: 'createdAt', @@ -3947,6 +4070,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '08806161-5d03-4777-8825-b5cff93de042', type: 'UUID', name: 'activityId', @@ -3968,6 +4092,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9ff04b9e-f87c-44df-82ed-518748ca0d81', type: 'UUID', name: 'rocketId', @@ -3989,6 +4114,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86438a20-0beb-4e73-93d9-cf91bfe9ac3f', type: 'UUID', name: 'id', @@ -4010,6 +4136,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f2ed506-e96e-48d3-8225-45a9c0b55e76', type: 'DATE_TIME', name: 'deletedAt', @@ -4031,6 +4158,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '867aff40-ddf5-4af8-a3f5-6359ab91eb2c', type: 'RELATION', name: 'person', @@ -4078,6 +4206,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c2708db4-5c1e-482b-bdd4-bd620612c15f', type: 'RELATION', name: 'opportunity', @@ -4160,6 +4289,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'eb926345-9d3c-4815-a044-bf1085b31cdc', type: 'UUID', name: 'assigneeId', @@ -4181,6 +4311,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7314a7d0-f318-4aa0-b8f3-db1953139d3f', type: 'TEXT', name: 'title', @@ -4202,6 +4333,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3e63dfad-08ba-422f-88e5-0c1ebffd6496', type: 'RELATION', name: 'author', @@ -4249,6 +4381,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5ceb1884-c3ff-4a31-9629-3ef599b1a461', type: 'DATE_TIME', name: 'updatedAt', @@ -4270,6 +4403,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ffdb396b-200a-4dc5-a3aa-e2c23b180ac0', type: 'TEXT', name: 'body', @@ -4291,6 +4425,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f1cfe74-f960-44f9-91f3-fc9d25a4b96b', type: 'RELATION', name: 'comments', @@ -4338,6 +4473,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '248b356d-5a70-458d-a10b-92d70512497b', type: 'DATE_TIME', name: 'createdAt', @@ -4359,6 +4495,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f1f7b1a-2c98-42ca-8f6c-784406d7bad8', type: 'UUID', name: 'authorId', @@ -4380,6 +4517,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '799c1e27-3fa5-4411-bfa2-a1f494d2434a', type: 'DATE_TIME', name: 'reminderAt', @@ -4401,6 +4539,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd5354bb-dd0a-4426-ac75-87e9ab171dc4', type: 'RELATION', name: 'assignee', @@ -4448,6 +4587,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd7b4584a-d0ed-49f8-b14c-198036b60fe8', type: 'DATE_TIME', name: 'dueAt', @@ -4469,6 +4609,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9b8808d3-be01-4595-9b20-ebb9553cd7db', type: 'DATE_TIME', name: 'completedAt', @@ -4490,6 +4631,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd0341ced-bdb0-4629-a816-d402df827bd7', type: 'TEXT', name: 'type', @@ -4511,6 +4653,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1dc0b9af-181a-4f2c-bd01-9c4bf355b9de', type: 'RELATION', name: 'activityTargets', @@ -4558,6 +4701,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b31fdd11-115b-4405-8265-c03329338f0c', type: 'DATE_TIME', name: 'deletedAt', @@ -4579,6 +4723,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bfa2ab3d-a55b-41ca-906b-9d497aee7ba8', type: 'RELATION', name: 'attachments', @@ -4626,6 +4771,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33b0ccb9-19fc-42d6-b7af-87bc4411692e', type: 'UUID', name: 'id', @@ -4682,6 +4828,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e166c79b-99c1-4fb4-9575-5cf7e7f4811f', type: 'BOOLEAN', name: 'isOrganizer', @@ -4703,6 +4850,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '34f2ac5d-bb7e-446c-ab52-18e722345a24', type: 'UUID', name: 'workspaceMemberId', @@ -4724,6 +4872,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c215138-fc5b-4be2-8c37-0acc7cb4e5a1', type: 'SELECT', name: 'responseStatus', @@ -4774,6 +4923,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '16b867dd-9fb1-43ab-8254-2478004b30b3', type: 'UUID', name: 'id', @@ -4795,6 +4945,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fdfea59a-624c-4af3-9901-1f86d1972b23', type: 'TEXT', name: 'displayName', @@ -4816,6 +4967,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f77bc8e-26dc-40b1-964a-f0748feb193a', type: 'RELATION', name: 'person', @@ -4863,6 +5015,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '597d153f-8724-4d61-8863-8bfae905721f', type: 'RELATION', name: 'calendarEvent', @@ -4910,6 +5063,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '83d94e19-f9f3-47bb-a277-6935af6ae69d', type: 'UUID', name: 'personId', @@ -4931,6 +5085,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cc27c5b5-44cc-4f19-a56e-8ce172e2ab37', type: 'DATE_TIME', name: 'updatedAt', @@ -4952,6 +5107,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '22c34bd1-550b-4b5c-a2a4-fbb475a4420b', type: 'DATE_TIME', name: 'createdAt', @@ -4973,6 +5129,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7b7792da-246d-4015-855a-ea029f0b8a02', type: 'DATE_TIME', name: 'deletedAt', @@ -4994,6 +5151,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7794c913-c52f-4c06-921e-2b391b63e51e', type: 'UUID', name: 'calendarEventId', @@ -5015,6 +5173,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '30fd4e8a-2089-4443-ac06-5cd53d9a3fcf', type: 'RELATION', name: 'workspaceMember', @@ -5062,6 +5221,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c97d9e28-d807-4fff-ba2c-c72f99087f89', type: 'TEXT', name: 'handle', @@ -5118,6 +5278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e8a5a86c-b1ce-4db1-8d51-d5b01d2b361b', type: 'DATE_TIME', name: 'updatedAt', @@ -5139,6 +5300,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '09a753b7-a5f9-4810-ae2f-389982f593c3', type: 'DATE_TIME', name: 'createdAt', @@ -5160,6 +5322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7881aa82-a7ed-4090-923d-f1c0cf16a486', type: 'DATE_TIME', name: 'deletedAt', @@ -5181,6 +5344,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7f1d2847-9f16-457f-a57c-de36c401286a', type: 'UUID', name: 'recordId', @@ -5202,6 +5366,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1db6f800-bb46-45a2-a324-cfe52362ed9a', type: 'UUID', name: 'id', @@ -5223,6 +5388,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '785dc6cb-77dc-4bdd-be44-60ad8f1f45da', type: 'UUID', name: 'workspaceMemberId', @@ -5244,6 +5410,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3d6dc314-877d-4aaa-88bd-364dc50f780b', type: 'RELATION', name: 'workspaceMember', @@ -5291,6 +5458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80fe2b13-5cb0-48a0-9341-aec08481628f', type: 'RAW_JSON', name: 'context', @@ -5313,6 +5481,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0383ac8e-6138-4e06-a828-bfc391e53d01', type: 'TEXT', name: 'name', @@ -5334,6 +5503,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f4f0343a-8241-492a-8156-18d40c75f46a', type: 'RAW_JSON', name: 'properties', @@ -5355,6 +5525,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8282a28-724f-487b-a9fa-f0d10c919237', type: 'TEXT', name: 'objectName', @@ -5376,6 +5547,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd85abbad-9616-456f-978c-4cced740490c', type: 'TEXT', name: 'objectMetadataId', @@ -5432,6 +5604,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '818ec7cd-9181-4c72-ad99-c19b00fde065', type: 'TEXT', name: 'syncCursor', @@ -5454,6 +5627,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '93658b38-cb56-4d2b-93f8-3a4c7714f7c4', type: 'TEXT', name: 'handle', @@ -5475,6 +5649,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'af61423d-0891-4e91-b6d3-2f7ed6363916', type: 'UUID', name: 'connectedAccountId', @@ -5496,6 +5671,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e921674b-2e9f-4f19-a067-920685cf9164', type: 'SELECT', name: 'visibility', @@ -5532,6 +5708,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0abc3baf-2797-4f88-8565-a3ffa4468b55', type: 'DATE_TIME', name: 'deletedAt', @@ -5553,6 +5730,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e567e960-f3c8-4ef0-b810-d8a4bc6a4f7d', type: 'BOOLEAN', name: 'isSyncEnabled', @@ -5574,6 +5752,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb3531fe-a008-4505-820b-0cb2c365b05d', type: 'DATE_TIME', name: 'createdAt', @@ -5595,6 +5774,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9a353c7-25ba-403f-8157-0d8bee911cec', type: 'UUID', name: 'id', @@ -5616,6 +5796,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd1596020-b595-4bf9-b3c1-782f9b49f41b', type: 'RELATION', name: 'calendarChannelEventAssociations', @@ -5663,6 +5844,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd757fd20-3568-411f-a2ba-ec2f7b30dc55', type: 'SELECT', name: 'syncStatus', @@ -5720,6 +5902,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '529b36bb-42d1-4361-ac0a-e683557cc879', type: 'DATE_TIME', name: 'updatedAt', @@ -5741,6 +5924,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8d7cb56f-29f1-40ad-90cf-4497bee669a1', type: 'SELECT', name: 'contactAutoCreationPolicy', @@ -5792,6 +5976,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c933e11e-7820-42e4-a589-389c2f314add', type: 'RELATION', name: 'connectedAccount', @@ -5839,6 +6024,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '01e23a9d-d8b7-47c6-a1f8-7e012ae9f54a', type: 'DATE_TIME', name: 'syncStageStartedAt', @@ -5860,6 +6046,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9c5f7d31-5b5e-4af1-9918-859ce66b6c08', type: 'SELECT', name: 'syncStage', @@ -5924,6 +6111,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c81da27-4ff4-4bf4-8937-9a8ee627e46c', type: 'NUMBER', name: 'throttleFailureCount', @@ -5945,6 +6133,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5c73b1cc-ddda-46a8-a517-f0bb5f0c5f60', type: 'BOOLEAN', name: 'isContactAutoCreationEnabled', @@ -6001,6 +6190,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0cc37cbd-c7ce-4898-b34d-5da7736e7b54', type: 'DATE_TIME', name: 'updatedAt', @@ -6022,6 +6212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '537e956c-58bb-4ed4-8127-beb0f2d04dd2', type: 'RELATION', name: 'messages', @@ -6069,6 +6260,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '728f4580-9220-4130-9a25-c56669ad0e43', type: 'DATE_TIME', name: 'deletedAt', @@ -6090,6 +6282,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fe0508ef-6e8e-4422-b822-67899af4aa58', type: 'DATE_TIME', name: 'createdAt', @@ -6111,6 +6304,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '823a20db-9431-422c-b0a1-4f559b992651', type: 'UUID', name: 'id', @@ -6167,6 +6361,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7011922c-2271-4960-81eb-b9f9ae3ae00c', type: 'DATE_TIME', name: 'updatedAt', @@ -6188,6 +6383,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37abc2ae-9f44-4bc0-8277-e3ddfd54738c', type: 'RELATION', name: 'people', @@ -6235,6 +6431,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c264b8c8-1260-410b-aa34-d69b14bba19b', type: 'LINKS', name: 'domainName', @@ -6261,6 +6458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '73c7c0bc-a328-488f-b357-fd9dc332ab75', type: 'BOOLEAN', name: 'visaSponsorship', @@ -6282,6 +6480,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b08a8600-2c4f-4e8a-8f32-6ffe7041e569', type: 'ADDRESS', name: 'address', @@ -6312,6 +6511,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '107fd869-fa4a-4ca5-b6b1-a918ec78851e', type: 'POSITION', name: 'position', @@ -6333,6 +6533,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f0ab2d8-3cdf-49d1-a8be-ef204e871968', type: 'NUMBER', name: 'employees', @@ -6354,6 +6555,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9677b321-e8fc-4d1b-9d7f-145a5dea0001', type: 'RELATION', name: 'favorites', @@ -6401,6 +6603,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8a21fcb9-5ee7-498c-a09d-1d3137be0540', type: 'DATE_TIME', name: 'deletedAt', @@ -6422,6 +6625,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '300545a5-6ca7-487a-8374-a662bab5d717', type: 'UUID', name: 'accountOwnerId', @@ -6444,6 +6648,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e41f22fd-ecd5-4b28-a2ed-a04d2a017c19', type: 'CURRENCY', name: 'annualRecurringRevenue', @@ -6469,6 +6674,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '24cc7f7b-e8d3-4c12-a3c5-caa5ccf61523', type: 'UUID', name: 'id', @@ -6490,6 +6696,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8421d3d3-d5ac-4065-a431-95780fda2ce7', type: 'RELATION', name: 'opportunities', @@ -6537,6 +6744,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2395970c-d0a2-43df-9f55-58299e930b34', type: 'RELATION', name: 'attachments', @@ -6584,6 +6792,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3b52848e-e419-4361-8fbc-3d4ed19f1956', type: 'TEXT', name: 'name', @@ -6605,6 +6814,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f23c37a-9971-4060-a861-19f030848b90', type: 'LINKS', name: 'xLink', @@ -6630,6 +6840,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c5103301-686a-4c1f-86d1-69e32a4a34ae', type: 'DATE_TIME', name: 'createdAt', @@ -6651,6 +6862,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '81907e01-90e3-412a-9aa5-9ae8352b679d', type: 'ACTOR', name: 'createdBy', @@ -6675,6 +6887,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f8393bb6-05c2-44d7-bff8-4a7671b43f15', type: 'RELATION', name: 'timelineActivities', @@ -6722,6 +6935,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4882e808-7cd2-487f-911f-ab2d9353e60d', type: 'RELATION', name: 'accountOwner', @@ -6770,6 +6984,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8cf3568-4d04-43cf-b8c2-2070a7eb0e4e', type: 'MULTI_SELECT', name: 'workPolicy', @@ -6813,6 +7028,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd69eb854-c043-43d9-a40e-65a0649fd1a9', type: 'RELATION', name: 'taskTargets', @@ -6860,6 +7076,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '531c4b2e-94a0-46f4-9395-277c3239413d', type: 'RELATION', name: 'noteTargets', @@ -6907,6 +7124,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'de10d71e-4cfa-4322-b967-761871e69bc0', type: 'LINKS', name: 'introVideo', @@ -6932,6 +7150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2e632fe-1c08-4c12-9aba-88f8595bf5be', type: 'RELATION', name: 'activityTargets', @@ -6979,6 +7198,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '16838104-8adf-45a3-93bf-e97551240b66', type: 'LINKS', name: 'linkedinLink', @@ -7004,6 +7224,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '195db745-305e-40f0-b740-d071c5c19214', type: 'TEXT', name: 'tagline', @@ -7025,6 +7246,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2d8e886a-3ebf-474c-8c7a-909e9d7fcc6f', type: 'BOOLEAN', name: 'idealCustomerProfile', @@ -7082,6 +7304,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c496110c-239a-4dd4-bdbd-022ca4fdc62c', type: 'RELATION', name: 'calendarEvent', @@ -7129,6 +7352,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8505bc8b-e978-401a-8983-f1f1e64aa26d', type: 'UUID', name: 'id', @@ -7150,6 +7374,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '18e21e78-56c2-492a-8082-f1e8ceb72e14', type: 'UUID', name: 'calendarChannelId', @@ -7171,6 +7396,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b61d8723-b739-42b6-b23d-b82e09b34669', type: 'DATE_TIME', name: 'deletedAt', @@ -7192,6 +7418,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '97a0be23-ba2e-4af3-8503-d4c27c293a37', type: 'DATE_TIME', name: 'createdAt', @@ -7213,6 +7440,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '906e35f2-e2d1-45d5-8326-cb7712a19e60', type: 'RELATION', name: 'calendarChannel', @@ -7260,6 +7488,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa298945-e331-40de-a6db-855b58b99d05', type: 'TEXT', name: 'eventExternalId', @@ -7281,6 +7510,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ec16ef5-2331-4a1e-b429-0e4fc3d9576f', type: 'DATE_TIME', name: 'updatedAt', @@ -7302,6 +7532,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2c765e90-ce86-4f6b-a528-94e38b5aaf54', type: 'UUID', name: 'calendarEventId', @@ -7358,6 +7589,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78eec6b7-ab9c-4dee-ac64-2b9e40b467f6', type: 'TEXT', name: 'name', @@ -7379,6 +7611,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd2653ce4-585b-4a38-9e09-4ae8e2afae51', type: 'DATE_TIME', name: 'expiresAt', @@ -7400,6 +7633,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e5cd8cb-e800-4b25-a9e6-cfb6d51623c6', type: 'DATE_TIME', name: 'updatedAt', @@ -7421,6 +7655,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '68704d45-b248-4e41-a07b-21d5874663bb', type: 'DATE_TIME', name: 'revokedAt', @@ -7442,6 +7677,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77ef9f04-627b-4708-b400-076629ed9f20', type: 'DATE_TIME', name: 'createdAt', @@ -7463,6 +7699,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc4892f0-a890-4a49-90ae-5a7999665785', type: 'UUID', name: 'id', @@ -7484,6 +7721,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bbe022a1-426f-40c2-ad1d-8294ef127c0b', type: 'DATE_TIME', name: 'deletedAt', @@ -7540,6 +7778,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9d2499b3-403c-473f-b61f-61bbea97afeb', type: 'UUID', name: 'noteId', @@ -7561,6 +7800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0d232853-5ae9-43f3-ac4a-248c50e2a64e', type: 'UUID', name: 'taskId', @@ -7582,6 +7822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8e506212-1b66-4981-95d6-f0ac83f5d869', type: 'RELATION', name: 'person', @@ -7629,6 +7870,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc005581-351b-4fa0-9b39-da5bbe2554b7', type: 'RELATION', name: 'task', @@ -7676,6 +7918,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86e97af0-ec16-4937-b5b6-e4531027be82', type: 'UUID', name: 'rocketId', @@ -7697,6 +7940,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c33a0768-3f55-4c7b-aa1a-07aeacf3fb85', type: 'UUID', name: 'viewId', @@ -7718,6 +7962,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'afc385d4-22d1-4f83-a67a-46450df368e9', type: 'DATE_TIME', name: 'updatedAt', @@ -7739,6 +7984,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '90a28b04-0214-424f-afad-172b7cd28073', type: 'UUID', name: 'workflowId', @@ -7760,6 +8006,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6a245087-243d-450b-9f00-c951d417d4ef', type: 'UUID', name: 'personId', @@ -7781,6 +8028,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '14e0bf40-d4eb-4893-a5f2-3df3ce749996', type: 'UUID', name: 'workspaceMemberId', @@ -7802,6 +8050,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ecb3679-5ff7-4b55-9ed3-9927bc9e184b', type: 'RELATION', name: 'note', @@ -7849,6 +8098,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3a561744-45bd-45a6-af94-bfb4d4f508fe', type: 'DATE_TIME', name: 'createdAt', @@ -7870,6 +8120,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '46ff8942-360f-4837-9a83-007739c8ba05', type: 'RELATION', name: 'view', @@ -7917,6 +8168,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6e104959-9dfb-42a7-80da-0ddb0dab12f9', type: 'UUID', name: 'opportunityId', @@ -7938,6 +8190,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f112f2a8-4f21-454d-b3a7-fcd85f0eab72', type: 'NUMBER', name: 'position', @@ -7959,6 +8212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4102fb0-c6e3-47e8-a810-71e0b6453705', type: 'DATE_TIME', name: 'deletedAt', @@ -7980,6 +8234,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a1555234-ff28-4a04-871c-31008b39e442', type: 'UUID', name: 'id', @@ -8001,6 +8256,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '10509b5e-71c0-49e8-9cb8-d0ff7ee8691b', type: 'UUID', name: 'companyId', @@ -8022,6 +8278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'eed525bd-edf3-4030-9f09-8ff68226a6a0', type: 'RELATION', name: 'workflow', @@ -8069,6 +8326,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5b559e4c-eb75-4a1e-b904-a486d2328b24', type: 'RELATION', name: 'workspaceMember', @@ -8116,6 +8374,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7155582f-4bbc-4643-be0c-38165b8a282f', type: 'RELATION', name: 'company', @@ -8163,6 +8422,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '11e6c0a0-e1fa-4931-a705-8725a79afe24', type: 'RELATION', name: 'rocket', @@ -8210,6 +8470,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d3858e1-e4aa-484f-b422-8bbefa9409c8', type: 'RELATION', name: 'opportunity', @@ -8292,6 +8553,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '974ab999-a946-409f-820c-aa2e2c21f3ce', type: 'DATE_TIME', name: 'updatedAt', @@ -8313,6 +8575,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '70a9b04f-0d11-41a1-bce8-e7ac8bb0ed5d', type: 'DATE_TIME', name: 'createdAt', @@ -8334,6 +8597,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da1dda8a-b7b6-4166-b11b-740a8414706b', type: 'UUID', name: 'authorId', @@ -8355,6 +8619,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3b07941b-15b6-4468-8e2f-52abf7ff36b3', type: 'TEXT', name: 'body', @@ -8376,6 +8641,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8f985123-68d4-4e8a-b75b-85a75f0f071e', type: 'UUID', name: 'id', @@ -8397,6 +8663,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e48bc6a2-211c-46aa-9f22-1859aedac28e', type: 'RELATION', name: 'activity', @@ -8444,6 +8711,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c39eb95c-2e9f-4fd3-abc3-103986dd21bb', type: 'UUID', name: 'activityId', @@ -8465,6 +8733,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '571e3b8c-0c80-4292-93b1-c73b4d976b05', type: 'DATE_TIME', name: 'deletedAt', @@ -8486,6 +8755,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b1ead0df-5b1c-4c00-b0c5-05a67ec37327', type: 'RELATION', name: 'author', @@ -8568,6 +8838,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c1f5af5-c2d5-4f38-9fd4-6ce854e693d3', type: 'DATE_TIME', name: 'updatedAt', @@ -8589,6 +8860,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f06f5946-e54f-458d-9c77-47d6d8fbd995', type: 'DATE_TIME', name: 'endsAt', @@ -8610,6 +8882,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bc8be6a6-4707-498a-9e79-8ffb86e92a43', type: 'RELATION', name: 'calendarEventParticipants', @@ -8657,6 +8930,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6bfac067-9c6c-4b4d-bdaf-ecf7737b2599', type: 'TEXT', name: 'iCalUID', @@ -8678,6 +8952,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '45bd7c28-f450-4487-a556-1fd85be68beb', type: 'TEXT', name: 'title', @@ -8699,6 +8974,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '52d1ed74-b3d5-4339-b408-6f4c8dbc3969', type: 'DATE_TIME', name: 'createdAt', @@ -8720,6 +8996,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4078641f-c8b4-418c-9e7a-0bfdc411c4d9', type: 'DATE_TIME', name: 'externalUpdatedAt', @@ -8741,6 +9018,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ae4f8ff1-47f1-4b84-b61b-c519c939a409', type: 'TEXT', name: 'conferenceSolution', @@ -8762,6 +9040,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a77794e2-adea-40cb-a0f9-a91a6d4494ed', type: 'DATE_TIME', name: 'startsAt', @@ -8783,6 +9062,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '62d6a06b-0083-4702-be85-edd6ca882816', type: 'TEXT', name: 'location', @@ -8804,6 +9084,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '567c7852-6dc5-4c6e-826d-e4b253614e60', type: 'TEXT', name: 'recurringEventExternalId', @@ -8825,6 +9106,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c0ed400f-b0cf-4fe1-a929-89698dc020a5', type: 'DATE_TIME', name: 'externalCreatedAt', @@ -8846,6 +9128,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da0d1cc9-be28-4b41-9ddf-48041702024b', type: 'TEXT', name: 'description', @@ -8867,6 +9150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '52db308a-e428-4efd-95ed-2b0e19cbdc92', type: 'RELATION', name: 'calendarChannelEventAssociations', @@ -8914,6 +9198,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '63904769-6145-4230-b294-c4554c36a273', type: 'BOOLEAN', name: 'isCanceled', @@ -8935,6 +9220,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48fac512-a780-4c44-b1dc-f178bd8ab3f8', type: 'DATE_TIME', name: 'deletedAt', @@ -8956,6 +9242,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1ba833a8-68ca-4396-b3ed-9a7411e1dc4f', type: 'UUID', name: 'id', @@ -8977,6 +9264,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3050381d-7191-4d18-be27-9a92cbefb57a', type: 'LINKS', name: 'conferenceLink', @@ -9002,6 +9290,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b6cf9c5d-e923-4026-9316-c3a513ce7c12', type: 'BOOLEAN', name: 'isFullDay', @@ -9058,6 +9347,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ac76a0ff-f5c1-4127-b354-0b3ce2b3696b', type: 'RELATION', name: 'favorites', @@ -9105,6 +9395,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e32f7a81-d208-4c14-afb4-a4befc938670', type: 'RELATION', name: 'assignedActivities', @@ -9152,6 +9443,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '85df4525-aed1-4e46-b718-b5bc963da41d', type: 'RELATION', name: 'auditLogs', @@ -9199,6 +9491,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a1ffe54a-6b76-47fa-bd25-df479a31eee2', type: 'FULL_NAME', name: 'name', @@ -9223,6 +9516,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7370da8c-294f-4671-91e5-7f87f4dccc1e', type: 'RELATION', name: 'calendarEventParticipants', @@ -9270,6 +9564,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '13531c23-42ca-4b1e-a6e3-3fcfad74a3e9', type: 'RELATION', name: 'connectedAccounts', @@ -9317,6 +9612,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a5fc2711-0e3d-40ac-938a-8beafeac1f57', type: 'RELATION', name: 'timelineActivities', @@ -9364,6 +9660,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb548a08-0706-4021-833d-e527c23e2a48', type: 'RELATION', name: 'accountOwnerForCompanies', @@ -9411,6 +9708,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fc4f41c0-01ea-428d-9e0c-e2496acc765b', type: 'TEXT', name: 'avatarUrl', @@ -9432,6 +9730,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f74a56bf-9230-4452-b6a0-099ba9f7de0d', type: 'RELATION', name: 'authoredComments', @@ -9479,6 +9778,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2895a0d-0e4f-4673-a778-2b99cac1ab20', type: 'UUID', name: 'userId', @@ -9500,6 +9800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4cceec11-98cc-41c6-b08c-b04887a0ac22', type: 'DATE_TIME', name: 'createdAt', @@ -9521,6 +9822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1dc4eef9-d576-4106-b19e-2fc91777470d', type: 'TEXT', name: 'timeZone', @@ -9542,6 +9844,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f548d63-6cf6-4116-ae5a-03fcc35ffd1d', type: 'UUID', name: 'id', @@ -9563,6 +9866,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6cc143e8-f88a-4d75-a52c-d5c7f7419d97', type: 'SELECT', name: 'timeFormat', @@ -9606,6 +9910,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b9f4e812-721f-4c83-9f1e-42b79042d905', type: 'DATE_TIME', name: 'updatedAt', @@ -9627,6 +9932,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '12e6ccfa-23ef-4b68-a043-6012bc7b9c67', type: 'TEXT', name: 'locale', @@ -9648,6 +9954,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b75d64ca-eb86-439d-ad61-3f23efec07e4', type: 'TEXT', name: 'userEmail', @@ -9669,6 +9976,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd63010d-24f1-4e62-9b49-b4728bb3bd81', type: 'DATE_TIME', name: 'deletedAt', @@ -9690,6 +9998,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f173a8e9-af79-4377-9b21-5cb1ca27bc87', type: 'TEXT', name: 'colorScheme', @@ -9711,6 +10020,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f76b3faa-0bc6-45ed-9654-0421171a1f1a', type: 'RELATION', name: 'authoredActivities', @@ -9758,6 +10068,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c5df2d21-5db8-4e72-82bf-aeb3ec984ca9', type: 'RELATION', name: 'authoredAttachments', @@ -9805,6 +10116,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd82db237-ca6d-4bee-8d69-dfa0f753707b', type: 'RELATION', name: 'messageParticipants', @@ -9852,6 +10164,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e64fef03-fda1-4b5c-8894-d1bee725e7e2', type: 'RELATION', name: 'blocklist', @@ -9899,6 +10212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3653df1b-5687-45c1-a2bf-afc261fe85a8', type: 'RELATION', name: 'assignedTasks', @@ -9946,6 +10260,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '18a0276f-ce5e-4fc7-81fc-c32f6dd844f3', type: 'SELECT', name: 'dateFormat', @@ -10031,6 +10346,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f58f100f-8ee9-4a0c-8f35-8bdcb561d586', type: 'DATE_TIME', name: 'createdAt', @@ -10052,6 +10368,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a3740f53-215a-477d-82be-b57265731d83', type: 'DATE_TIME', name: 'deletedAt', @@ -10073,6 +10390,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '67dbcf4d-f15b-4703-ba84-4bc2b9903579', type: 'UUID', name: 'id', @@ -10094,6 +10412,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd26efc67-251a-4bdd-a585-177717e298a6', type: 'TEXT', name: 'eventName', @@ -10115,6 +10434,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c8d7ed48-3d0e-47fa-a4af-5a62e578c128', type: 'UUID', name: 'workflowId', @@ -10137,6 +10457,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a424fba4-d3f4-41b1-bf9a-4b809ad628a9', type: 'RELATION', name: 'workflow', @@ -10184,6 +10505,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aabd6970-7bf9-488b-8411-5bc654574d58', type: 'DATE_TIME', name: 'updatedAt', @@ -10240,6 +10562,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ee4d98d0-37a4-42cc-8794-653099d4df54', type: 'BOOLEAN', name: 'isVisible', @@ -10261,6 +10584,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '643509ce-5666-4264-8b09-f095e23a1624', type: 'NUMBER', name: 'position', @@ -10282,6 +10606,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c672e744-21f3-4d23-abd8-fcc03fad503a', type: 'UUID', name: 'viewId', @@ -10303,6 +10628,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '49e8ada7-3a80-49c7-869a-3ebfdae35387', type: 'NUMBER', name: 'size', @@ -10324,6 +10650,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd7510e1-4f22-4376-8436-19d7d631ea77', type: 'DATE_TIME', name: 'createdAt', @@ -10345,6 +10672,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f74ba2c1-22ab-4827-85ad-d2dbbe2a9b51', type: 'RELATION', name: 'view', @@ -10392,6 +10720,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '959d36dc-493b-4e08-ae3d-38680bab1d0d', type: 'UUID', name: 'id', @@ -10413,6 +10742,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '300b7bf6-e1fa-4cd2-a601-7a125b7bf1b8', type: 'DATE_TIME', name: 'deletedAt', @@ -10434,6 +10764,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f7acd2a-ccd1-437e-b80a-bf4555a7c034', type: 'UUID', name: 'fieldMetadataId', @@ -10455,6 +10786,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '217be100-e82e-4aae-9566-b091823a5466', type: 'DATE_TIME', name: 'updatedAt', @@ -10511,6 +10843,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '330a6b96-c7eb-41ae-962e-eb528dc16aaf', type: 'DATE_TIME', name: 'updatedAt', @@ -10532,6 +10865,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '45fe30a3-141f-4908-be8b-b826f84edb75', type: 'UUID', name: 'viewId', @@ -10553,6 +10887,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7924f4d6-c92b-436f-a86d-26b2dcc521aa', type: 'TEXT', name: 'direction', @@ -10574,6 +10909,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fc34ddd1-7f8a-4f23-a2b6-9a6d3165cc0a', type: 'DATE_TIME', name: 'deletedAt', @@ -10595,6 +10931,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4f8f882-dec2-4d6b-a141-936e87d3fd27', type: 'UUID', name: 'id', @@ -10616,6 +10953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '25ae0938-795a-491a-b029-e4672412e85f', type: 'RELATION', name: 'view', @@ -10663,6 +11001,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '540715e7-6531-4746-b567-b0a7fcda60ef', type: 'UUID', name: 'fieldMetadataId', @@ -10684,6 +11023,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26889121-d2d9-49dc-86ac-51c64d123197', type: 'DATE_TIME', name: 'createdAt', @@ -10739,6 +11079,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9aed4be2-3434-489f-a8a3-384311ee585e', type: 'RELATION', name: 'activityTargets', @@ -10786,6 +11127,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '13794481-6a3a-48cb-80c2-109b7558f7b3', type: 'RELATION', name: 'attachments', @@ -10833,6 +11175,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '79da64bc-eea3-476e-801b-f08c86a8c337', type: 'RELATION', name: 'favorites', @@ -10880,6 +11223,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0a688ec2-f55c-4485-a7a9-9438c19bcbe3', type: 'ACTOR', name: 'createdBy', @@ -10904,6 +11248,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '17cbb6ed-1acc-43e9-a802-96a2b373a067', type: 'DATE_TIME', name: 'updatedAt', @@ -10925,6 +11270,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ba68ee30-5dc6-47db-bf6f-db25d829feb5', type: 'TEXT', name: 'name', @@ -10946,6 +11292,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ff73fd8a-9b60-4480-82d0-7b96c3c3aab6', type: 'POSITION', name: 'position', @@ -10967,6 +11314,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '83515891-eb29-472e-9cde-4a1d42b6855d', type: 'RELATION', name: 'taskTargets', @@ -11014,6 +11362,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '73428c65-a426-4c59-b50b-0dea5ffe9bf0', type: 'DATE_TIME', name: 'createdAt', @@ -11035,6 +11384,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6c65c781-9def-4c6c-98d4-25d3c0c085b8', type: 'UUID', name: 'id', @@ -11056,6 +11406,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2643f4a-319e-49d4-a7b7-cbfff4712bf7', type: 'DATE_TIME', name: 'deletedAt', @@ -11077,6 +11428,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '75f64c23-e9a5-4ada-8dc6-3c2c2ea27280', type: 'RELATION', name: 'noteTargets', @@ -11124,6 +11476,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aeefa434-6843-4b47-92f6-3ce6d8e93860', type: 'RELATION', name: 'timelineActivities', @@ -11206,6 +11559,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fa957d80-c61a-4298-aab9-54e8fba2110d', type: 'UUID', name: 'id', @@ -11227,6 +11581,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f966b379-0438-4b9c-8696-3edf18c197f7', type: 'RELATION', name: 'workspaceMember', @@ -11274,6 +11629,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37f7671b-aa7c-4fed-b50c-9b7cc7f59aa8', type: 'TEXT', name: 'handle', @@ -11295,6 +11651,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '862261a3-5473-4e16-9220-16444ee99243', type: 'DATE_TIME', name: 'updatedAt', @@ -11316,6 +11673,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1233f40e-cc97-4281-a8a8-c23de8961693', type: 'DATE_TIME', name: 'createdAt', @@ -11337,6 +11695,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a75f6045-cf57-4b05-960b-5719ce2037c9', type: 'DATE_TIME', name: 'deletedAt', @@ -11358,6 +11717,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c30e3bff-ce4e-4e64-80e3-bf417ceefa25', type: 'UUID', name: 'workspaceMemberId', @@ -11414,6 +11774,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2d00a390-5ae4-40d9-8d6c-6abbcdc0bc75', type: 'DATE_TIME', name: 'updatedAt', @@ -11435,6 +11796,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2aab6f08-2cab-4beb-8775-1ad8b89c3313', type: 'ACTOR', name: 'createdBy', @@ -11459,6 +11821,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f40e2edc-c0dd-46d8-9a01-a0afd33a00db', type: 'UUID', name: 'id', @@ -11480,6 +11843,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '421de2e5-36a1-4fb2-ae00-4e2f62dd67e6', type: 'UUID', name: 'workflowVersionId', @@ -11502,6 +11866,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c9e837bb-4516-43ad-8756-7a9c2ad33ca9', type: 'DATE_TIME', name: 'createdAt', @@ -11523,6 +11888,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aec86cc0-1fb6-4ff0-b1df-85b74ee6a974', type: 'UUID', name: 'workflowId', @@ -11544,6 +11910,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b38c3f7d-d455-4f1c-b674-66b86c0d56cc', type: 'RELATION', name: 'workflowVersion', @@ -11591,6 +11958,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '446d4c37-71ac-4a61-8b65-16d70250cbc5', type: 'SELECT', name: 'status', @@ -11641,6 +12009,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ed4a200-4d2e-4d4d-81b2-45240ae9e1b8', type: 'DATE_TIME', name: 'endedAt', @@ -11662,6 +12031,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a5444b51-1ad5-46ae-b9d9-ef5f1def9232', type: 'RELATION', name: 'workflow', @@ -11709,6 +12079,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b388b202-371a-4663-9299-afc6be461a8c', type: 'DATE_TIME', name: 'deletedAt', @@ -11730,6 +12101,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2e84a165-18af-492f-8662-14fbd5852b0c', type: 'DATE_TIME', name: 'startedAt', @@ -11786,6 +12158,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f41b722-fbc4-44d9-9315-51498900e157', type: 'DATE_TIME', name: 'updatedAt', @@ -11807,6 +12180,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d53ad9c-bdf4-4660-b4b6-e79162d13c39', type: 'RELATION', name: 'favorites', @@ -11854,6 +12228,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e7df66e0-50cc-4047-9b91-8f5ee1ff7246', type: 'DATE_TIME', name: 'createdAt', @@ -11875,6 +12250,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '34ec585e-b42e-4e63-8e4b-7f9bf6a4e79b', type: 'DATE_TIME', name: 'deletedAt', @@ -11896,6 +12272,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '22b735aa-95d7-44fe-a4a6-965171e3f7b7', type: 'DATE_TIME', name: 'dueAt', @@ -11917,6 +12294,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fea3a2f3-73ca-4142-8d15-3b8877b68cee', type: 'UUID', name: 'id', @@ -11938,6 +12316,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4f82dbad-cf61-4dd1-ad90-4cec71b288be', type: 'RELATION', name: 'attachments', @@ -11985,6 +12364,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b52cbdab-0c97-4601-b5ad-de766ec9f940', type: 'SELECT', name: 'status', @@ -12028,6 +12408,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1eea9af9-53a3-4085-9391-4a2fad697eb7', type: 'RELATION', name: 'timelineActivities', @@ -12075,6 +12456,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78360243-9830-4f40-a515-59cd8faf88b1', type: 'RICH_TEXT', name: 'body', @@ -12096,6 +12478,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33d7eb92-8231-417c-8efe-eb837c6ccadf', type: 'ACTOR', name: 'createdBy', @@ -12120,6 +12503,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f9a8761-e5e6-492c-87ab-401c7b0e25cd', type: 'UUID', name: 'assigneeId', @@ -12141,6 +12525,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7a062a59-07da-4d2d-a0e8-f79f87e2e5e3', type: 'RELATION', name: 'assignee', @@ -12188,6 +12573,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80932806-6350-4941-a291-4d1430275d65', type: 'RELATION', name: 'taskTargets', @@ -12235,6 +12621,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'daf389ba-49e6-4753-8a5a-a0330c5ab154', type: 'POSITION', name: 'position', @@ -12256,6 +12643,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0413dccd-00a6-4abd-967a-45233e8cf666', type: 'TEXT', name: 'title', @@ -12312,6 +12700,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '38f5127c-ab53-45bc-90b9-a3883d697eb9', type: 'DATE_TIME', name: 'deletedAt', @@ -12333,6 +12722,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2a7002e-19f2-4f16-8b5b-ea452aeb7104', type: 'TEXT', name: 'lastPublishedVersionId', @@ -12354,6 +12744,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd6fb9120-9aa5-4fdf-a84a-2805bb359855', type: 'RELATION', name: 'versions', @@ -12401,6 +12792,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '99d7e7e6-ac6a-4b74-9129-d4a9759bc928', type: 'DATE_TIME', name: 'createdAt', @@ -12422,6 +12814,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '06a5a424-930c-4465-9943-2a9c486f2038', type: 'UUID', name: 'id', @@ -12443,6 +12836,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '650b02ef-32b5-4b10-bdab-a49f4e3b7a9b', type: 'MULTI_SELECT', name: 'statuses', @@ -12484,6 +12878,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b14865e9-26a6-4ae8-930d-d22c21c7696c', type: 'RELATION', name: 'eventListeners', @@ -12532,6 +12927,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4f32f14b-5009-4624-8b19-e65084368349', type: 'RELATION', name: 'runs', @@ -12579,6 +12975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bab376b1-a7d9-48b8-88b7-f302bc6d483d', type: 'TEXT', name: 'name', @@ -12600,6 +12997,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '141281c7-5b28-41d4-99bd-31c1e4c88e9b', type: 'RELATION', name: 'favorites', @@ -12647,6 +13045,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f21077b2-283f-4362-98fd-e5ea5f87d621', type: 'POSITION', name: 'position', @@ -12668,6 +13067,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b063853c-a442-4b52-8f89-f750c44a2e04', type: 'DATE_TIME', name: 'updatedAt', @@ -12725,6 +13125,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6615546f-8744-4b36-83d5-59c1ef72e845', type: 'UUID', name: 'id', @@ -12746,6 +13147,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '299c9bdc-5137-4d2b-8225-ddb81a720bfe', type: 'UUID', name: 'rocketId', @@ -12767,6 +13169,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8626f50f-1d39-40e7-a05b-528c42fe4313', type: 'RELATION', name: 'task', @@ -12814,6 +13217,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f2f27d9e-c959-4dee-aed4-f87c64229c3f', type: 'RELATION', name: 'rocket', @@ -12861,6 +13265,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '62e278a5-d719-4b49-a820-fc5ed358311e', type: 'DATE_TIME', name: 'happensAt', @@ -12882,6 +13287,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '145c8790-abd1-49fb-9857-650da78c6717', type: 'RELATION', name: 'company', @@ -12929,6 +13335,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '08dd3999-ca07-43cf-b872-a2bed317aa6a', type: 'TEXT', name: 'name', @@ -12950,6 +13357,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bbdf103a-60cb-4c39-a982-1704e26f6735', type: 'UUID', name: 'noteId', @@ -12971,6 +13379,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e9d4989-fdf2-4e71-90af-aae2ef0b4923', type: 'RELATION', name: 'person', @@ -13018,6 +13427,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1b496b65-948d-4614-889f-2a9e0a6292f3', type: 'UUID', name: 'linkedObjectMetadataId', @@ -13039,6 +13449,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd22914c-60d6-4e45-9936-9ff345d3a5bb', type: 'DATE_TIME', name: 'updatedAt', @@ -13060,6 +13471,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77f9059d-33f5-4fa1-8641-4976d38eaeb7', type: 'TEXT', name: 'linkedRecordCachedName', @@ -13081,6 +13493,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '137a88ef-9c08-414c-adcc-2d450624acf8', type: 'RELATION', name: 'workspaceMember', @@ -13128,6 +13541,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2df9370-1560-47f6-89b6-88293de46572', type: 'UUID', name: 'personId', @@ -13149,6 +13563,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9cb24716-07e8-4c54-a0c8-7005a619314e', type: 'RAW_JSON', name: 'properties', @@ -13170,6 +13585,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bc5277df-a1ec-4ae2-affe-e42aed88f0b9', type: 'UUID', name: 'taskId', @@ -13191,6 +13607,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cda06c7c-9c62-4579-b3ba-2e8275c604b1', type: 'DATE_TIME', name: 'createdAt', @@ -13212,6 +13629,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '95fa7d72-fd85-4a32-a489-a5d03199debb', type: 'UUID', name: 'workspaceMemberId', @@ -13233,6 +13651,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d16560a-4a8f-448c-8289-70e3e98ad3b4', type: 'UUID', name: 'linkedRecordId', @@ -13254,6 +13673,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c646eaf5-2754-4924-a212-c5747b0d1d41', type: 'UUID', name: 'companyId', @@ -13275,6 +13695,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '88a829b3-6d66-4e53-b1ad-ed02e544e4d2', type: 'RELATION', name: 'opportunity', @@ -13322,6 +13743,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'db7ab4e8-9d93-4f45-bbbf-f68eb31776cb', type: 'RELATION', name: 'note', @@ -13369,6 +13791,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1ee1c4bc-7281-4eff-bc32-6afaff324477', type: 'DATE_TIME', name: 'deletedAt', @@ -13390,6 +13813,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '225d1acc-2ccc-458d-acd5-06f7d8647a38', type: 'UUID', name: 'opportunityId', @@ -13447,6 +13871,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f926708e-cc19-4e5f-8684-d14a1a1bc7df', type: 'RELATION', name: 'pointOfContactForOpportunities', @@ -13495,6 +13920,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5a063961-bffe-4d68-a0c8-e86b6b26f85e', type: 'FULL_NAME', name: 'name', @@ -13519,6 +13945,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd02d52c8-379b-476d-84d6-1222c2179db7', type: 'LINKS', name: 'linkedinLink', @@ -13544,6 +13971,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '504f7e23-1476-422b-ac1d-5d86d3d33022', type: 'RELATION', name: 'company', @@ -13591,6 +14019,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'daab9749-7d39-44cb-9557-23e0e257aaad', type: 'RELATION', name: 'attachments', @@ -13638,6 +14067,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6e46c199-d4f9-4c60-b9bb-19da859991b4', type: 'RELATION', name: 'timelineActivities', @@ -13685,6 +14115,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa5e7931-e042-45d5-af4a-e4c22979a3b7', type: 'RELATION', name: 'calendarEventParticipants', @@ -13732,6 +14163,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8a3d68a-a1b1-4912-aa13-51f088c6a754', type: 'DATE_TIME', name: 'deletedAt', @@ -13753,6 +14185,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e442f814-f8a6-44e9-b60c-d736afec87dd', type: 'DATE_TIME', name: 'createdAt', @@ -13774,6 +14207,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a91e4125-7885-4b3c-9672-0f6d2fc49c07', type: 'DATE_TIME', name: 'updatedAt', @@ -13795,6 +14229,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4a9a6c3e-633d-47d6-99bc-b8e6e9b6aad9', type: 'TEXT', name: 'jobTitle', @@ -13816,6 +14251,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b801b0d2-2a24-42be-b878-ef439ef7ea78', type: 'TEXT', name: 'intro', @@ -13837,6 +14273,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c188f5b7-2259-4207-87d2-5232ec775029', type: 'RELATION', name: 'favorites', @@ -13884,9 +14321,10 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5cdf5de5-6c7f-4787-b47d-de5e3787e670', type: 'MULTI_SELECT', - name: 'workPrefereance', + name: 'workPreference', label: 'Work Preference', description: "Person's Work Preference", icon: 'IconHome', @@ -13927,6 +14365,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1315ca79-3abd-4b7f-917e-8949db3e01f3', type: 'RATING', name: 'performanceRating', @@ -13979,6 +14418,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '749e13b2-5430-4993-83db-635c6ff11d1a', type: 'LINKS', name: 'xLink', @@ -14004,6 +14444,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f7b1621-5da6-439a-927f-948fd2dd6f29', type: 'RELATION', name: 'noteTargets', @@ -14051,6 +14492,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '759da4e9-e9c6-4827-9802-5b7b4021448f', type: 'TEXT', name: 'city', @@ -14072,6 +14514,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48f76fc3-3c82-463d-afa7-977011eed7c8', type: 'UUID', name: 'companyId', @@ -14093,6 +14536,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '23c64ee1-4935-4a25-b401-afc08a0967fd', type: 'RELATION', name: 'activityTargets', @@ -14140,6 +14584,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd4a5fecc-285b-42d7-8eb4-96fc2a6838c4', type: 'PHONES', name: 'phones', @@ -14165,6 +14610,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb5900a5-29ff-46bb-8bbb-7dbedd844e29', type: 'ACTOR', name: 'createdBy', @@ -14189,6 +14635,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54fbea0f-ce3f-4d28-8fa4-abcfe1ae3d54', type: 'UUID', name: 'id', @@ -14210,6 +14657,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '718ee8a6-a294-4609-91d4-7ab0e83d996f', type: 'POSITION', name: 'position', @@ -14231,6 +14679,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '779bdf5a-a28d-48be-8d02-b6ca93851829', type: 'RELATION', name: 'messageParticipants', @@ -14278,6 +14727,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7f8249b8-5919-4fee-81cf-b4dfdb89cf4d', type: 'EMAILS', name: 'emails', @@ -14302,6 +14752,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b328a712-5dc3-457a-aa56-8631f1b57248', type: 'RELATION', name: 'taskTargets', @@ -14349,6 +14800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '377efd0d-9798-4522-b9db-73c19bf55e26', type: 'TEXT', name: 'avatarUrl', @@ -14370,6 +14822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3a34e81d-d5b1-417d-9831-a355040a6f44', type: 'PHONES', name: 'whatsapp', @@ -14430,6 +14883,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f115229b-f5b3-4623-ac80-f8bf3df5e077', type: 'RELATION', name: 'favorites', @@ -14477,6 +14931,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed46299b-4006-45b4-aaa4-2dc7c0c25613', type: 'DATE_TIME', name: 'deletedAt', @@ -14498,6 +14953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54952f24-63cc-475e-ab4c-b0f3ad6400bc', type: 'UUID', name: 'id', @@ -14519,6 +14975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bef43ac8-834a-4a86-8bfb-5bed6cd94a57', type: 'RELATION', name: 'noteTargets', @@ -14566,6 +15023,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '210b544b-46f7-422d-84e7-328ef270f081', type: 'RELATION', name: 'timelineActivities', @@ -14613,6 +15071,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd80706c2-3c5d-491f-9759-b05b25004799', type: 'POSITION', name: 'position', @@ -14634,6 +15093,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2238e043-a33f-4e8d-99d6-386cd0d2ea3b', type: 'DATE_TIME', name: 'updatedAt', @@ -14655,6 +15115,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c996b13e-a787-44f7-ad7a-2ba22afd46bc', type: 'ACTOR', name: 'createdBy', @@ -14679,6 +15140,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '61375724-caaa-46b1-8e50-4b2f23afac71', type: 'RICH_TEXT', name: 'body', @@ -14700,6 +15162,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ffc4d21-aa5d-4f0f-bc91-61c72660f2ba', type: 'TEXT', name: 'title', @@ -14721,6 +15184,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '27b7400b-e754-4db9-b895-0a9252a015bf', type: 'DATE_TIME', name: 'createdAt', @@ -14742,6 +15206,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80479763-1c19-4465-a62f-cd5e37c9165a', type: 'RELATION', name: 'attachments', @@ -14824,6 +15289,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4e4f0f26-89bd-41eb-bc00-a591e041ca7d', type: 'DATE_TIME', name: 'updatedAt', @@ -14845,6 +15311,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '362cb4b7-dda1-4136-9385-4f5402b4f700', type: 'UUID', name: 'id', @@ -14866,6 +15333,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd13848d4-b41e-43b0-87ca-ab40c00fd739', type: 'DATE_TIME', name: 'createdAt', @@ -14887,6 +15355,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '81685712-ecaa-4d04-a8d9-e6c93a6bfe7a', type: 'TEXT', name: 'displayValue', @@ -14908,6 +15377,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f72701d1-8337-4640-84b7-4570cfbe96aa', type: 'UUID', name: 'viewId', @@ -14929,6 +15399,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e323bb1b-8018-4b6f-b065-37c979caf7fc', type: 'DATE_TIME', name: 'deletedAt', @@ -14950,6 +15421,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '646c0bfc-071d-44f3-b8d4-428061106500', type: 'UUID', name: 'fieldMetadataId', @@ -14971,6 +15443,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '016a2312-ac04-4d46-b7c7-2d6e24e363c8', type: 'TEXT', name: 'value', @@ -14992,6 +15465,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e09cc7ab-2750-4cd9-86e1-c257dac8b390', type: 'TEXT', name: 'operand', @@ -15013,6 +15487,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd1829b60-e886-4dac-8cf3-0d3b84da093d', type: 'RELATION', name: 'view', @@ -15095,6 +15570,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd208a4e-3e5e-4192-83a1-adbc5b9123a1', type: 'RAW_JSON', name: 'steps', @@ -15116,6 +15592,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fe28de2e-8979-4cfd-9b66-03e67e93406b', type: 'UUID', name: 'id', @@ -15137,6 +15614,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dcdf9413-04c8-44f7-ace7-a11950ce3019', type: 'SELECT', name: 'status', @@ -15187,6 +15665,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef3d8797-2f17-4f31-9970-697fc230df7f', type: 'RELATION', name: 'runs', @@ -15234,6 +15713,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ebe58c33-17d2-418b-b33f-f5c3907e97d7', type: 'DATE_TIME', name: 'deletedAt', @@ -15255,6 +15735,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1b223e47-9228-41c4-a420-ff6ed516393e', type: 'DATE_TIME', name: 'createdAt', @@ -15276,6 +15757,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54cde78b-4bd4-436b-b10b-e6de37494161', type: 'RELATION', name: 'workflow', @@ -15323,6 +15805,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4ebb0eb9-9ad6-4e5b-b01b-837b0e2c0718', type: 'RAW_JSON', name: 'trigger', @@ -15344,6 +15827,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef2dd597-faaa-4b1d-96b7-5953cd8c8539', type: 'TEXT', name: 'name', @@ -15365,6 +15849,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9f1d5c8-ce95-48c3-a4f3-0909aea7e322', type: 'DATE_TIME', name: 'updatedAt', @@ -15386,6 +15871,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '182c0466-9e03-4500-8cf7-22673e05b299', type: 'UUID', name: 'workflowId', @@ -15442,6 +15928,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '426142c4-5a52-4105-a880-387b6dba6362', type: 'DATE_TIME', name: 'deletedAt', @@ -15463,6 +15950,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4d459669-2ffd-4fd0-b28b-57d8a1eb9434', type: 'UUID', name: 'personId', @@ -15484,6 +15972,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1a6dd2bf-3da7-492d-80de-a8891c5307b7', type: 'UUID', name: 'opportunityId', @@ -15505,6 +15994,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3669c8b2-ba73-4e9b-be61-10a2023955fd', type: 'RELATION', name: 'activity', @@ -15552,6 +16042,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '023f376c-023a-4cae-89e8-961add0b3743', type: 'UUID', name: 'id', @@ -15573,6 +16064,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6a5833a9-0741-44f0-948d-424a60d3264e', type: 'RELATION', name: 'note', @@ -15620,6 +16112,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d3d2f23-c3e6-4ee0-b62e-7668e4f8147d', type: 'TEXT', name: 'fullPath', @@ -15641,6 +16134,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9a80298-6f72-4b2f-a859-2bd355d36735', type: 'RELATION', name: 'author', @@ -15688,6 +16182,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '42dbb8d7-320b-460d-a25e-943222ae2a9b', type: 'TEXT', name: 'type', @@ -15709,6 +16204,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a7971798-8b85-477e-8a1e-9b2f2fb5da6d', type: 'UUID', name: 'rocketId', @@ -15730,6 +16226,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '610633e7-0221-4838-adaf-71943d18a5ca', type: 'RELATION', name: 'person', @@ -15777,6 +16274,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26668e62-39e7-4842-a198-a657f44206f8', type: 'RELATION', name: 'task', @@ -15824,6 +16322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1433d739-29ed-450c-8216-6afea26d21fb', type: 'RELATION', name: 'company', @@ -15871,6 +16370,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1a8466d6-8be7-458a-a7ea-cdf11b4fe31d', type: 'DATE_TIME', name: 'updatedAt', @@ -15892,6 +16392,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7dc968df-7127-4e05-b63e-f2e9809324ee', type: 'UUID', name: 'noteId', @@ -15913,6 +16414,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ffec116-99d2-402d-adea-68b731be4c74', type: 'UUID', name: 'taskId', @@ -15934,6 +16436,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd76c9b48-5a3e-445f-be15-948de8ba2fc2', type: 'TEXT', name: 'name', @@ -15955,6 +16458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fa9d964c-3d30-4d8b-bc57-9b382053e9e3', type: 'RELATION', name: 'opportunity', @@ -16002,6 +16506,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '133dcd8e-aa07-4c9e-a337-92dabd5f7d03', type: 'UUID', name: 'activityId', @@ -16023,6 +16528,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd10c5485-159b-4700-a709-37d3049a8778', type: 'DATE_TIME', name: 'createdAt', @@ -16044,6 +16550,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '60f8c4fb-1ab9-44a7-a5bd-e89a0349feb7', type: 'RELATION', name: 'rocket', @@ -16091,6 +16598,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'adaa359c-c834-44bb-8639-44ef1affce2f', type: 'UUID', name: 'authorId', @@ -16112,6 +16620,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3486e0e2-053c-4e27-9d5d-c27b5dd739ea', type: 'UUID', name: 'companyId', @@ -16168,6 +16677,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '827beccf-c3ca-4f24-a349-5d7c8690ac95', type: 'TEXT', name: 'headerMessageId', @@ -16189,6 +16699,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f51b5646-06b0-45e4-960f-5f8eaeb18c83', type: 'DATE_TIME', name: 'deletedAt', @@ -16210,6 +16721,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f32d4ef7-cadd-4ce5-84ce-a95fd76fde05', type: 'DATE_TIME', name: 'updatedAt', @@ -16231,6 +16743,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c36f257c-2318-4b61-9c63-666e1fc0810c', type: 'DATE_TIME', name: 'receivedAt', @@ -16252,6 +16765,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86a6dca6-5ad5-4576-b8f4-4be343e573de', type: 'RELATION', name: 'messageChannelMessageAssociations', @@ -16299,6 +16813,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '606cef74-d9c3-4abc-b6ae-bb778f518e49', type: 'RELATION', name: 'messageParticipants', @@ -16346,6 +16861,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '21c34ce8-7505-4d6c-9fc8-218dd8532a25', type: 'UUID', name: 'id', @@ -16367,6 +16883,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48f20f05-700d-4609-965b-8a954bf07e8d', type: 'UUID', name: 'messageThreadId', @@ -16388,6 +16905,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '11f38fa4-e7b8-4275-b0b2-59688cb2eed8', type: 'RELATION', name: 'messageThread', @@ -16435,6 +16953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2697ab29-9991-4188-9569-8bc6fc079ec6', type: 'TEXT', name: 'subject', @@ -16456,6 +16975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f6a75fcf-f8a3-41dd-b1d0-efce8358e2d2', type: 'TEXT', name: 'text', @@ -16477,6 +16997,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '29f11f81-aab0-4b96-971f-24d84c81f1bf', type: 'DATE_TIME', name: 'createdAt', @@ -16533,6 +17054,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '61fe3c78-ab04-4a83-9a40-8560f7285abe', type: 'RELATION', name: 'favorites', @@ -16580,6 +17102,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '027418ee-6028-456f-a570-0b032d35b07f', type: 'RELATION', name: 'viewFields', @@ -16627,6 +17150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4385a72b-bb9d-4ac6-9c08-d9853c468726', type: 'UUID', name: 'id', @@ -16648,6 +17172,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '421bf244-013a-4962-970a-37150cf38057', type: 'TEXT', name: 'type', @@ -16669,6 +17194,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8ee5c1d6-718a-4e43-b468-91563270ae35', type: 'TEXT', name: 'icon', @@ -16690,6 +17216,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '073a7e52-0c68-4853-a500-5470b026c914', type: 'SELECT', name: 'key', @@ -16719,6 +17246,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed944e8e-e84d-40ef-aa44-b423453c23f9', type: 'BOOLEAN', name: 'isCompact', @@ -16740,6 +17268,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5454b125-73c2-438b-991b-eb361bcd6295', type: 'TEXT', name: 'kanbanFieldMetadataId', @@ -16761,6 +17290,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7974ab3b-7d05-4738-a05b-b76840f98328', type: 'RELATION', name: 'viewFilters', @@ -16808,6 +17338,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '23b99490-be92-4edc-a939-07e0f64f13eb', type: 'RELATION', name: 'viewSorts', @@ -16855,6 +17386,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '36f1f251-121a-461c-aef5-aba2c9fa39a6', type: 'UUID', name: 'objectMetadataId', @@ -16876,6 +17408,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2fcf2221-da1f-4c19-acdc-adfb089ea219', type: 'POSITION', name: 'position', @@ -16897,6 +17430,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a0e02b0d-6681-41da-a8a8-48c0bc5ba690', type: 'DATE_TIME', name: 'createdAt', @@ -16918,6 +17452,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dbc64e7b-2b59-4e9e-9e1e-bcd9e9f5a57a', type: 'DATE_TIME', name: 'deletedAt', @@ -16939,6 +17474,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f5c9b9fc-87fd-4a74-af09-28a330a53bec', type: 'DATE_TIME', name: 'updatedAt', @@ -16960,6 +17496,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a24df79d-a05a-45e7-8791-b71957dff236', type: 'TEXT', name: 'name', diff --git a/packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts b/packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts similarity index 100% rename from packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts rename to packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts diff --git a/packages/twenty-front/src/testing/mock-data/metadata.ts b/packages/twenty-front/src/testing/mock-data/metadata.ts deleted file mode 100644 index f739afd46647..000000000000 --- a/packages/twenty-front/src/testing/mock-data/metadata.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; -import { - FieldMetadataType, - ObjectEdge, - ObjectMetadataItemsQuery, -} from '~/generated-metadata/graphql'; -import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result'; - -// TODO: replace with new mock -const customObjectMetadataItemEdge: ObjectEdge = { - __typename: 'objectEdge', - node: { - __typename: 'object', - id: 'efa1addc-a9cb-4789-b99e-a060fa84f982', - dataSourceId: 'd36e6a2d-28bc-459d-afd5-fe18e4405729', - nameSingular: 'myCustom', - namePlural: 'myCustoms', - labelSingular: 'My Custom', - labelPlural: 'My Customs', - description: 'A custom object example', - icon: 'IconLayoutCollage', - isCustom: true, - isRemote: false, - isActive: true, - isSystem: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - labelIdentifierFieldMetadataId: null, - imageIdentifierFieldMetadataId: null, - fields: { - __typename: 'ObjectFieldsConnection', - pageInfo: { - __typename: 'PageInfo', - hasNextPage: false, - hasPreviousPage: false, - startCursor: 'YXJyYXljb25uZWN0aW9uOjA=', - endCursor: 'YXJyYXljb25uZWN0aW9uOjEz', - }, - edges: [ - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'ea83af89-be10-49af-a605-10c3392ae007', - type: 'RELATION', - name: 'companies', - label: 'Companies', - description: 'A custom Relation example', - icon: 'IconTag', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: { - relationId: '1ec22b36-9e3c-4f24-8cf6-6c387ec3f243', - __typename: 'RelationDefinition', - direction: 'ONE_TO_MANY', - sourceObjectMetadata: { - __typename: 'object', - id: 'efa1addc-a9cb-4789-b99e-a060fa84f982', - nameSingular: 'myCustom', - namePlural: 'myCustoms', - }, - sourceFieldMetadata: { - __typename: 'field', - id: 'ea83af89-be10-49af-a605-10c3392ae007', - name: 'companies', - }, - targetObjectMetadata: { - __typename: 'object', - id: 'dba899da-7d88-41ac-b70e-5ea612ab4b2e', - nameSingular: 'company', - namePlural: 'companies', - }, - targetFieldMetadata: { - __typename: 'field', - id: 'c9607ed7-168d-4743-a56a-689ffcfffe98', - name: 'myCustom', - }, - }, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'c5384d2a-9ec3-4e1b-b93f-86f53f122169', - type: 'UUID', - name: 'objectMetadataId', - label: 'Object Metadata Id', - description: 'View target object', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'bb4d96be-e4d9-47a9-812d-fcdfb063ebf3', - type: 'POSITION', - name: 'position', - label: 'Position', - description: 'View position', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'f20c68aa-3930-41c4-9f79-45dceda506df', - type: 'TEXT', - name: 'name', - label: 'Name', - description: 'Custom name', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: "''", - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'a3ef848d-660a-4aef-9cd4-5baf25ce36ed', - type: 'DATE_TIME', - name: 'createdAt', - label: 'Creation date', - description: 'Creation date', - icon: 'IconCalendar', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'now', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: '92f3e27c-041d-45b2-b2bd-46db2b1aec3f', - type: 'DATE_TIME', - name: 'updatedAt', - label: 'Update date', - description: 'Update date', - icon: 'IconCalendar', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'now', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: '8d7987eb-99e8-4e54-a86c-86b3bd07d2be', - type: 'UUID', - name: 'id', - label: 'Id', - description: 'Id', - icon: 'Icon123', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'uuid', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'e07fcc3f-beec-4d91-8488-9d1d2cfa5f99', - type: FieldMetadataType.Select, - name: 'priority', - label: 'Priority', - description: 'A custom Select example', - icon: 'IconWarning', - isCustom: true, - isActive: true, - isSystem: false, - options: [ - { - id: '2b98dc02-0d99-4f3e-890e-e2e6b8f3196c', - value: 'LOW', - label: 'Low', - color: 'turquoise', - }, - { - id: 'd925a8de-d8ec-4b59-a079-64f4012e3311', - value: 'MEDIUM', - label: 'Medium', - color: 'yellow', - }, - { - id: '6f6e1421-8a42-4d4a-bf76-465b5f84b6d2', - value: 'HIGH', - label: 'High', - color: 'red', - }, - ], - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - ], - }, - }, -} as ObjectEdge; - -export const mockedObjectMetadataItemsQueryResult = { - ...mockedStandardObjectMetadataQueryResult, - objects: { - ...mockedStandardObjectMetadataQueryResult.objects, - edges: [ - ...mockedStandardObjectMetadataQueryResult.objects.edges, - customObjectMetadataItemEdge, - ], - }, -} as ObjectMetadataItemsQuery; - -export const mockedObjectMetadataItems = - mapPaginatedObjectMetadataItemsToObjectMetadataItems({ - pagedObjectMetadataItems: mockedObjectMetadataItemsQueryResult, - }); - -export const mockedCompanyObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'company', -) as ObjectMetadataItem; - -export const mockedPersonObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'person', -) as ObjectMetadataItem; - -export const mockedCustomObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'myCustom', -) as ObjectMetadataItem; - -export const mockedOpportunityObjectMetadataItem = - mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'opportunity', - ) as ObjectMetadataItem; diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index f7a4c2727e22..cc483bd1e570 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -26,6 +26,7 @@ type MockedUser = Pick< locale: string; defaultWorkspace: Workspace; workspaces: Array<{ workspace: Workspace }>; + workspaceMembers: WorkspaceMember[]; }; export const avatarUrl = @@ -107,6 +108,7 @@ export const mockedUserData: MockedUser = { defaultWorkspace: mockDefaultWorkspace, locale: 'en', workspaces: [{ workspace: mockDefaultWorkspace }], + workspaceMembers: [mockedWorkspaceMemberData], onboardingStatus: OnboardingStatus.Completed, userVars: {}, }; diff --git a/packages/twenty-front/src/testing/mock-data/view-fields.ts b/packages/twenty-front/src/testing/mock-data/view-fields.ts index b68cfb706a27..325e3610ccd7 100644 --- a/packages/twenty-front/src/testing/mock-data/view-fields.ts +++ b/packages/twenty-front/src/testing/mock-data/view-fields.ts @@ -1,240 +1,416 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedViewsData } from './views'; +const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const personObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', +); + export const mockedViewFieldsData = [ // Companies { id: '79035310-e955-4986-a4a4-73f9d9949c6a', - fieldMetadataId: '9e123592-cd2b-471c-8143-3cc0b46089ef', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[0].id, position: 0, isVisible: true, size: 180, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '2a96bbc8-d86d-439a-8e50-4b07ebd27750', - fieldMetadataId: 'domainName', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'domainName', + )?.id, viewId: mockedViewsData[0].id, position: 1, isVisible: true, size: 100, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '0c1b4c7b-6a3d-4fb0-bf2b-5d7c8fb844ed', - fieldMetadataId: 'accountOwner', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'accountOwner', + )?.id, viewId: mockedViewsData[0].id, position: 2, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'cc7f9560-32b5-4b82-8fd9-b05fe77c8cf7', - fieldMetadataId: 'createdAt', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[0].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3de4d078-3396-4480-be2d-6f3b1a228b0d', - fieldMetadataId: 'employees', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'employees', + )?.id, viewId: mockedViewsData[0].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '4650c8fb-0f1e-4342-88dc-adedae1445f9', - fieldMetadataId: 'linkedin', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, viewId: mockedViewsData[0].id, position: 5, isVisible: true, size: 170, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '727430bf-6ff8-4c85-9828-cbe72ac0fc27', - fieldMetadataId: 'address', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'address', + )?.id, viewId: mockedViewsData[0].id, position: 6, isVisible: true, size: 170, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + + // Companies v2 + { + id: '79035310-e955-4986-a4a4-73f9d9949c6a', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, + viewId: mockedViewsData[3].id, + position: 0, + isVisible: true, + size: 180, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '2a96bbc8-d86d-439a-8e50-4b07ebd27750', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'domainName', + )?.id, + viewId: mockedViewsData[3].id, + position: 1, + isVisible: true, + size: 100, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '0c1b4c7b-6a3d-4fb0-bf2b-5d7c8fb844ed', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'accountOwner', + )?.id, + viewId: mockedViewsData[3].id, + position: 2, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: 'cc7f9560-32b5-4b82-8fd9-b05fe77c8cf7', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, + viewId: mockedViewsData[3].id, + position: 3, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '3de4d078-3396-4480-be2d-6f3b1a228b0d', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'employees', + )?.id, + viewId: mockedViewsData[3].id, + position: 4, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '4650c8fb-0f1e-4342-88dc-adedae1445f9', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, + viewId: mockedViewsData[3].id, + position: 5, + isVisible: true, + size: 170, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '727430bf-6ff8-4c85-9828-cbe72ac0fc27', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'address', + )?.id, + viewId: mockedViewsData[3].id, + position: 6, + isVisible: true, + size: 170, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, // People { id: '28894146-4fde-4a16-a9ca-1a31b5b788b4', - fieldMetadataId: 'displayName', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[1].id, position: 0, isVisible: true, size: 210, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'e1e24864-8601-4cd8-8a63-09c1285f2e39', - fieldMetadataId: 'email', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'emails', + )?.id, viewId: mockedViewsData[1].id, position: 1, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '5a1df716-7211-445a-9f16-9783a00998a7', - fieldMetadataId: 'company', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'company', + )?.id, viewId: mockedViewsData[1].id, position: 2, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'a6e1197a-7e84-4d92-ace2-367c0bc46c49', - fieldMetadataId: 'phone', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'phones', + )?.id, viewId: mockedViewsData[1].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'c9343097-d14b-4559-a5fa-626c1527d39f', - fieldMetadataId: 'createdAt', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[1].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'a873e5f0-fed6-47e9-a712-6854eab3ec77', - fieldMetadataId: 'city', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'city', + )?.id, viewId: mockedViewsData[1].id, position: 5, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '66f134b8-5329-422f-b88e-83e6bb707eb5', - fieldMetadataId: 'jobTitle', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'jobTitle', + )?.id, viewId: mockedViewsData[1].id, position: 6, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '648faa24-cabb-482a-8578-ba3f09906017', - fieldMetadataId: 'linkedin', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, viewId: mockedViewsData[1].id, position: 7, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3a9e7f0d-a4ce-4ad5-aac7-3a24eb1a412d', - fieldMetadataId: 'x', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'xLink', + )?.id, viewId: mockedViewsData[1].id, position: 8, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, // Opportunities { id: '35a42e2d-83dd-4b57-ada6-f90616da706d', - fieldMetadataId: 'amount', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[2].id, position: 0, isVisible: true, size: 180, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3159acd8-463f-458d-bf9a-af8ac6f57dc0', - fieldMetadataId: 'closeDate', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'closeDate', + )?.id, viewId: mockedViewsData[2].id, position: 2, isVisible: true, size: 100, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'afc0819d-b694-4e3c-a2e6-25261aa3ed2c', - fieldMetadataId: 'company', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'company', + )?.id, viewId: mockedViewsData[2].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'ec0507bb-aedc-4695-ba96-d81bdeb9db83', - fieldMetadataId: 'createdAt', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[2].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3f1585f6-44f6-45c5-b840-bc05af5d0008', - fieldMetadataId: 'pointOfContact', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'pointOfContact', + )?.id, viewId: mockedViewsData[2].id, position: 5, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, ]; diff --git a/packages/twenty-front/src/testing/mock-data/views.ts b/packages/twenty-front/src/testing/mock-data/views.ts index d13c0dc3fdfb..3318e158b4f6 100644 --- a/packages/twenty-front/src/testing/mock-data/views.ts +++ b/packages/twenty-front/src/testing/mock-data/views.ts @@ -1,813 +1,22 @@ -import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; -import { ViewType } from '@/views/types/ViewType'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -export const viewQueryResultMock: RecordGqlConnection = { - __typename: 'ViewConnection', - totalCount: 6, - pageInfo: { - __typename: 'PageInfo', - hasNextPage: false, - hasPreviousPage: false, - startCursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==', - endCursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==', - }, - edges: [ - { - __typename: 'ViewEdge', - cursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==', - node: { - __typename: 'View', - position: 1, - updatedAt: '2024-07-11T10:21:33.304Z', - key: null, - id: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconLayoutKanban', - isCompact: false, - name: 'By Stage', - type: 'kanban' as ViewType, - kanbanFieldMetadataId: 'f74de381-4392-4662-a890-5ed3b5bd847d', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: '05ffd5e0-69b0-4774-843a-fbae12231e7d', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '573ae123-1eed-4671-8fff-d9ac9455b1b4', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'ae4f318f-5059-41ba-b365-22daa0b3cb0e', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'b5ac37dc-9f64-412f-a598-611bdb5d27f8', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: 'bda12277-2962-4b35-a549-665cbbe53483', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: 'f43e660f-bbf8-4a2f-aeb1-54890ac40f4b', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI1N2FkYTUyMy0zZDgzLTQzOTEtYThiOS0wZTkxOGUyNGE1MTkiXQ==', - node: { - __typename: 'View', - position: null, - updatedAt: '2024-07-12T09:52:15.595Z', - key: 'INDEX', - id: '57ada523-3d83-4391-a8b9-0e918e24a519', - objectMetadataId: '3561dbe5-39a2-40fa-a111-4af924e39908', - createdAt: '2024-07-12T09:52:15.595Z', - icon: 'IconListNumbers', - isCompact: false, - name: 'All Tests', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-16T12:58:56.823Z', - position: 2, - id: '39a026d9-8362-4a4c-9b35-3d23218122a7', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-16T12:58:56.823Z', - isVisible: true, - size: 100, - fieldMetadataId: '9918f304-99d9-4d5b-8351-c6b6f7cc38bb', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.598Z', - position: 0, - id: '3ab70930-e60a-4bfd-830a-57355121d889', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.598Z', - isVisible: true, - size: 180, - fieldMetadataId: 'f7f485fc-0c14-4b70-a180-0508699a5c14', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.604Z', - position: 1, - id: '43ec0b2c-d94f-4eaf-a4bc-f00d409661b5', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.604Z', - isVisible: true, - size: 180, - fieldMetadataId: '66645848-4100-4649-bc0e-d50281df2fd6', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.604Z', - position: 2, - id: '53f01c19-7042-4551-97d8-d36b6ae28602', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.604Z', - isVisible: true, - size: 180, - fieldMetadataId: '1b3caa7a-343a-4b4b-8c2e-3371cd1dd237', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-16T12:59:52.966Z', - position: 3, - id: 'ecbc275e-f937-4d00-b035-6225e6f87c90', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-16T12:59:46.864Z', - isVisible: true, - size: 209, - fieldMetadataId: '9e3e6ed9-7889-4979-bc15-c7803bf437f1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.605Z', - position: 3, - id: 'ed9f32e7-cd08-4bcf-b78f-838371cd282a', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.605Z', - isVisible: true, - size: 180, - fieldMetadataId: 'ffa953c4-d8e0-49af-b2ef-f16e238f4687', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI1ODJmMjI0Yy0zYzNmLTQxMjctYjFlZC0yOTcxZDI3ZTU0YTQiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconTargetArrow', - isCompact: false, - name: 'All Opportunities', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: '0f0cacad-7f1d-4667-a0e8-466cddad3e65', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: '19d0d674-9825-492d-bbd0-c1de494201dc', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: '77d6a102-7b8e-40c0-9d53-33e9a8d0df0f', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '8d1da76d-4056-4675-b2e5-907021c1b482', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: 'a5556adc-e4a0-4f71-aee3-2ff2a4e53b31', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'fa8cdb32-24f0-483d-a9f6-bc92f2704452', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI2NTM3M2UxZS0xNmU1LTRlNWYtOWJjMS1jMDlkOTAxNTZmMjciXQ==', - node: { - __typename: 'View', - position: null, - updatedAt: '2024-07-11T15:41:08.076Z', - key: null, - id: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc', - createdAt: '2024-07-11T15:41:08.076Z', - icon: 'IconBuildingSkyscraper', - isCompact: false, - name: 'All Companies L', - type: 'kanban', - kanbanFieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.141Z', - position: 5, - id: '00bf00d5-f257-4ed0-9a80-ce6d7fa2eace', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.141Z', - isVisible: true, - size: 170, - fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.082Z', - position: 4, - id: '08ccb08d-d279-4738-bdc0-32a0f9b01390', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.082Z', - isVisible: true, - size: 150, - fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.115Z', - position: 2, - id: '478c93ae-1dcc-4d79-b821-b53431348abe', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.115Z', - isVisible: true, - size: 150, - fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.083Z', - position: 7, - id: '4b28e7c9-f97b-4e86-80bf-ca7a1cc49f64', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.083Z', - isVisible: true, - size: 180, - fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.076Z', - position: 3, - id: '6402a5db-dc6f-433c-9de3-af19a6d71a28', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.076Z', - isVisible: true, - size: 150, - fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.141Z', - position: 1, - id: '97938fb7-d3a2-42a1-8c04-7ff59d18e41c', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.141Z', - isVisible: true, - size: 100, - fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.129Z', - position: 0, - id: 'c7429772-5214-49ee-9d96-c4c9ea929888', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.129Z', - isVisible: true, - size: 180, - fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.083Z', - position: 6, - id: 'ec211171-6676-40d2-acc2-5fa13f11ed00', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.083Z', - isVisible: true, - size: 170, - fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyJiZWU2NWJjNC05YmNiLTQ5YTgtOGVhNS0xYmQ5MjQxYjA5YzMiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - objectMetadataId: '48824ee2-367d-481f-b80b-ca1eeb85c4ab', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconUser', - isCompact: false, - name: 'All People', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: '2dc48490-3ee8-4ade-a979-d5326da33d43', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '34ed07ad-067a-4f5f-bdee-21a37616f96b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: '67af4225-56e0-4ef9-bcfc-4a551d676c2b', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 210, - fieldMetadataId: 'c485ed46-3f8a-4ee6-af70-628b9f18ad47', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: '796bdd63-cc83-4f8c-b538-9f8e9dfb1937', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4aadffed-1df4-4732-bb99-559f31a464af', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 7, - id: '80cb1229-5e05-45d4-89da-b2ec850ffb2f', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'a8298361-b7c8-4b6c-be6c-d33885e00237', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 8, - id: '835b104c-b9fc-4c9f-b659-3dc4bb54d9ef', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '125cbc00-7efb-473d-b0a6-581d3cf868dd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 6, - id: '8f494c43-6b63-4033-b303-0110698cf19c', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '22566388-8ece-43dc-8205-371e662716d4', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: 'b2d72e77-a323-4e2e-acef-598b6da04712', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'b328512c-ff13-431b-9c94-1018ef0bd53c', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'c7e1a253-9af8-498a-b579-adab742acf2d', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2eb50615-376c-45e8-b99b-440a92a912d3', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: 'cceae812-2687-49b5-a0c8-eb59956865e8', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'f34c04f8-ce2f-4c92-8dbc-9166c6e0d49f', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconBuildingSkyscraper', - isCompact: false, - name: 'All Companies', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: '1cb6aeed-8011-495f-9371-20bace45814a', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 6, - id: '3beb2130-bdc5-48d1-8cd0-22c5d0010ad2', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 170, - fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '5f73729b-9592-473a-8742-8e52b693c780', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:03.814Z', - position: 7, - id: '788627cb-0d3a-4659-ab4b-69deabf02f27', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T15:41:03.814Z', - isVisible: true, - size: 180, - fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: 'ac5797a1-2d29-42d2-b9fb-d679a945eec5', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'ae98037e-38f7-4fbf-8ae1-c0b6754c6311', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 180, - fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: 'b4d9f94e-0c4b-4422-839a-f2ceb293fde1', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 170, - fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'd45b1412-ff6b-41e5-86df-0fb778033bb3', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 100, - fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88', - }, - }, - ], - }, - }, - }, - ], -}; +const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const personObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', +); export const mockedViewsData = [ { id: '37a8a866-eb17-4e76-9382-03143a2f6a80', name: 'All companies', - objectMetadataId: '701aecf9-eb1c-4d84-9d94-b954b231b64b', + objectMetadataId: companyObjectMetadata?.id, type: 'table', icon: 'IconSkyline', key: 'INDEX', @@ -822,7 +31,7 @@ export const mockedViewsData = [ { id: '6095799e-b48f-4e00-b071-10818083593a', name: 'All people', - objectMetadataId: 'person', + objectMetadataId: personObjectMetadata?.id, type: 'table', icon: 'IconPerson', key: 'INDEX', @@ -836,7 +45,7 @@ export const mockedViewsData = [ { id: 'e26f66b7-f890-4a5c-b4d2-ec09987b5308', name: 'All opportunities', - objectMetadataId: 'company', + objectMetadataId: opportunityObjectMetadata?.id, type: 'kanban', icon: 'IconOpportunity', key: 'INDEX', @@ -850,7 +59,7 @@ export const mockedViewsData = [ { id: '5c307222-1dd5-4ff3-ab06-8d990e9b3c74', name: 'All companies (v2)', - objectMetadataId: '701aecf9-eb1c-4d84-9d94-b954b231b64b', + objectMetadataId: companyObjectMetadata?.id, type: 'table', icon: 'IconSkyline', key: 'INDEX', diff --git a/packages/twenty-front/src/utils/format/__tests__/number.test.ts b/packages/twenty-front/src/utils/format/__tests__/number.test.ts index 37237e03dd53..8b2f6687f8f1 100644 --- a/packages/twenty-front/src/utils/format/__tests__/number.test.ts +++ b/packages/twenty-front/src/utils/format/__tests__/number.test.ts @@ -6,12 +6,15 @@ describe('formatNumber', () => { expect(formatNumber(123)).toEqual('123'); }); it(`Should format decimal numbers correctly`, () => { - expect(formatNumber(123.92)).toEqual('123.92'); + expect(formatNumber(123.92, 2)).toEqual('123.92'); }); it(`Should format large numbers correctly`, () => { expect(formatNumber(1234567)).toEqual('1,234,567'); }); it(`Should format large numbers with a decimal point correctly`, () => { - expect(formatNumber(7654321.89)).toEqual('7,654,321.89'); + expect(formatNumber(7654321.89, 2)).toEqual('7,654,321.89'); + }); + it('should format apply decimals correctly', () => { + expect(formatNumber(123.456, 2)).toEqual('123.46'); }); }); diff --git a/packages/twenty-server/jest.config.ts b/packages/twenty-server/jest.config.ts index 00c1b6f06fdb..f25c9e66dcca 100644 --- a/packages/twenty-server/jest.config.ts +++ b/packages/twenty-server/jest.config.ts @@ -7,7 +7,7 @@ const jestConfig: JestConfigWithTsJest = { displayName: 'twenty-server', rootDir: './', testEnvironment: 'node', - transformIgnorePatterns: ['../../node_modules/'], + transformIgnorePatterns: ['/node_modules/'], testRegex: '.*\\.spec\\.ts$', transform: { '^.+\\.(t|j)s$': 'ts-jest', diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index dde92f42a8da..d5c8f91b9c69 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -1,6 +1,6 @@ { "name": "twenty-server", - "version": "0.31.canary", + "version": "0.31.0-canary", "description": "", "author": "", "private": true, diff --git a/packages/twenty-server/src/constants/assets-path.ts b/packages/twenty-server/src/constants/assets-path.ts index 44c7724db0b8..04766287433a 100644 --- a/packages/twenty-server/src/constants/assets-path.ts +++ b/packages/twenty-server/src/constants/assets-path.ts @@ -1,3 +1,8 @@ import path from 'path'; -export const ASSET_PATH = path.resolve(__dirname, `../../assets`); +// If the code is built through the testing module, assets are not output to the dist/assets directory. +const IS_BUILT_THROUGH_TESTING_MODULE = !__dirname.includes('/dist/'); + +export const ASSET_PATH = IS_BUILT_THROUGH_TESTING_MODULE + ? path.resolve(__dirname, `../`) + : path.resolve(__dirname, `../../assets`); diff --git a/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts b/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts index 330975f00d02..cf605a51244e 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts @@ -104,7 +104,7 @@ export const getDevSeedPeopleCustomFields = ( { workspaceId, type: FieldMetadataType.MULTI_SELECT, - name: 'workPrefereance', + name: 'workPreference', label: 'Work Preference', description: "Person's Work Preference", icon: 'IconHome', diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts index 4fec6c7ab443..625da7f23b07 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts @@ -31,6 +31,9 @@ describe('mapFieldMetadataToGraphqlQuery', () => { }); describe('should handle all field metadata types', () => { Object.values(FieldMetadataType).forEach((fieldMetadataType) => { + if (fieldMetadataType === FieldMetadataType.TS_VECTOR) { + return; + } it(`with field type ${fieldMetadataType}`, () => { const field = { type: fieldMetadataType, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts index 5c885c312dfc..5f10e3be0d75 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts @@ -10,7 +10,8 @@ describe('computeSchemaComponents', () => { it('should test all non-deprecated field types', () => { expect(fields.map((field) => field.type)).toEqual( Object.keys(FieldMetadataType).filter( - (key) => key !== FieldMetadataType.LINK, + (key) => + key !== FieldMetadataType.LINK && key !== FieldMetadataType.TS_VECTOR, ), ); }); @@ -21,6 +22,7 @@ describe('computeSchemaComponents', () => { ] as ObjectMetadataEntity[]), ).toEqual({ ObjectName: { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -195,6 +197,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -203,6 +206,7 @@ describe('computeSchemaComponents', () => { required: ['fieldNumber'], }, 'ObjectName for Update': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -377,6 +381,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -384,6 +389,7 @@ describe('computeSchemaComponents', () => { }, }, 'ObjectName for Response': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -558,6 +564,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, workspaceMemberId: { diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts index 8fed171beec6..3d3a8c2a8952 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts @@ -1,5 +1,5 @@ -import path, { join } from 'path'; import fs from 'fs/promises'; +import path, { join } from 'path'; import { ASSET_PATH } from 'src/constants/assets-path'; diff --git a/packages/twenty-server/test/people.integration-spec.ts b/packages/twenty-server/test/people.integration-spec.ts index fd568bd5ba40..28b981e22dbe 100644 --- a/packages/twenty-server/test/people.integration-spec.ts +++ b/packages/twenty-server/test/people.integration-spec.ts @@ -27,7 +27,7 @@ describe('peopleResolver (integration)', () => { whatsapp { primaryPhoneNumber } - workPrefereance + workPreference performanceRating } } @@ -69,7 +69,7 @@ describe('peopleResolver (integration)', () => { expect(people).toHaveProperty('companyId'); expect(people).toHaveProperty('intro'); expect(people).toHaveProperty('whatsapp'); - expect(people).toHaveProperty('workPrefereance'); + expect(people).toHaveProperty('workPreference'); expect(people).toHaveProperty('performanceRating'); } }); diff --git a/packages/twenty-server/test/serverless-functions.integration-spec.ts b/packages/twenty-server/test/serverless-functions.integration-spec.ts deleted file mode 100644 index b4b87ff7caed..000000000000 --- a/packages/twenty-server/test/serverless-functions.integration-spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('serverlessFunctionsResolver (integration)', () => { - it('should find many serverlessFunctions', () => { - const queryData = { - query: ` - query serverlessFunctions { - serverlessFunctions { - edges { - node { - id - name - description - sourceCodeHash - runtime - latestVersion - syncStatus - createdAt - updatedAt - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.serverlessFunctions; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const serverlessFunctions = edges[0].node; - - expect(serverlessFunctions).toHaveProperty('id'); - expect(serverlessFunctions).toHaveProperty('name'); - expect(serverlessFunctions).toHaveProperty('description'); - expect(serverlessFunctions).toHaveProperty('sourceCodeHash'); - expect(serverlessFunctions).toHaveProperty('runtime'); - expect(serverlessFunctions).toHaveProperty('latestVersion'); - expect(serverlessFunctions).toHaveProperty('syncStatus'); - expect(serverlessFunctions).toHaveProperty('createdAt'); - expect(serverlessFunctions).toHaveProperty('updatedAt'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/utils/setup-test.ts b/packages/twenty-server/test/utils/setup-test.ts index aac860fc79ee..bc206be689a6 100644 --- a/packages/twenty-server/test/utils/setup-test.ts +++ b/packages/twenty-server/test/utils/setup-test.ts @@ -1,5 +1,5 @@ -import 'tsconfig-paths/register'; import { JestConfigWithTsJest } from 'ts-jest'; +import 'tsconfig-paths/register'; import { createApp } from './create-app'; diff --git a/packages/twenty-ui/package.json b/packages/twenty-ui/package.json index 64e99d25f0a9..3e9aadc6ce64 100644 --- a/packages/twenty-ui/package.json +++ b/packages/twenty-ui/package.json @@ -1,6 +1,6 @@ { "name": "twenty-ui", - "version": "0.31.canary", + "version": "0.31.0-canary", "type": "module", "main": "./src/index.ts", "exports": { diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 84a8db29ff09..a7518f4e1510 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -185,7 +185,7 @@ export { IconVideo, IconWand, IconWorld, - IconX + IconX, } from '@tabler/icons-react'; export type { TablerIconsProps } from '@tabler/icons-react'; diff --git a/packages/twenty-website/package.json b/packages/twenty-website/package.json index e60004faf0f8..f3ca9e2137db 100644 --- a/packages/twenty-website/package.json +++ b/packages/twenty-website/package.json @@ -1,6 +1,6 @@ { "name": "twenty-website", - "version": "0.31.canary", + "version": "0.31.0-canary", "private": true, "scripts": { "nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js", From 23e7958218175ead1ba0d7a455e5e1c057719a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Sat, 5 Oct 2024 00:44:52 +0200 Subject: [PATCH 13/16] Fix syncStageStartedAt not correctly set (#7436) Fix syncStageStartedAt not correctly set --------- Co-authored-by: Charles Bochet --- .../common/services/message-channel-sync-status.service.ts | 1 + .../services/messaging-partial-message-list-fetch.service.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts index 19e6746ce1a3..35affc12d9a7 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts @@ -161,6 +161,7 @@ export class MessageChannelSyncStatusService { await messageChannelRepository.update(messageChannelIds, { syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, + syncStageStartedAt: new Date().toISOString(), }); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts index bdf13895045a..b80ebc9c86ad 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts @@ -54,7 +54,6 @@ export class MessagingPartialMessageListFetchService { }, { throttleFailureCount: 0, - syncStageStartedAt: null, }, ); From c73feb513a079aa289a0fa468ebac2ff0decf843 Mon Sep 17 00:00:00 2001 From: Gabriel Utzig Date: Fri, 4 Oct 2024 20:23:02 -0300 Subject: [PATCH 14/16] fix: Center Functions Empty state (#7378) ## Description - This PR fix #7012 - It changes the logic behind the rendering of `SettingsPageContainer` component. Now, the component is only rendered when the page content is not blank. ## Changes | Before | After | |--------|--------| | ![image](https://github.com/user-attachments/assets/98b64370-f145-41a2-a829-f86ae9687f73) | ![image](https://github.com/user-attachments/assets/4cb0cc32-a669-4151-9444-4bc734bd2909) |
Details

This change aligns the behavior of the settings page with the existing logic found on the `/rockets` page

Co-authored-by: Charles Bochet --- .../SettingsServerlessFunctionsTable.tsx | 43 ++++++++++--------- .../SettingsServerlessFunctions.tsx | 9 ++-- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx index 93785116af7e..286a90faadca 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx @@ -1,3 +1,4 @@ +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsServerlessFunctionsFieldItemTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsFieldItemTableRow'; import { SettingsServerlessFunctionsTableEmpty } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty'; import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions'; @@ -24,26 +25,28 @@ export const SettingsServerlessFunctionsTable = () => { return ( <> {serverlessFunctions.length ? ( - - - Name - Runtime - - - - {serverlessFunctions.map( - (serverlessFunction: ServerlessFunction) => ( - - ), - )} - -
+ + + + Name + Runtime + + + + {serverlessFunctions.map( + (serverlessFunction: ServerlessFunction) => ( + + ), + )} + +
+
) : ( )} diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx index 00dcabb77bdc..5f9e58ce7e2a 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx @@ -1,4 +1,3 @@ -import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsServerlessFunctionsTable } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; @@ -35,11 +34,9 @@ export const SettingsServerlessFunctions = () => { }, ]} > - -
- -
-
+
+ +
); }; From 2472b3faaf54d600df05bf633658a941bebbba1d Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 5 Oct 2024 09:56:51 +0200 Subject: [PATCH 15/16] Diagnostics CI size issue --- .github/workflows/ci-front.yaml | 2 + packages/twenty-front/src/App.tsx | 72 +++++++++++++++---------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci-front.yaml b/.github/workflows/ci-front.yaml index aa6955723796..b61506709f93 100644 --- a/.github/workflows/ci-front.yaml +++ b/.github/workflows/ci-front.yaml @@ -31,6 +31,8 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/workflows/actions/yarn-install + - name: Diagnostic disk space issue + run: df -h - name: Front / Restore Storybook Task Cache uses: ./.github/workflows/actions/task-cache with: diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 1c6adcfdf60e..e8757d1b9913 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -58,43 +58,41 @@ const ProvidersThatNeedRouterContext = () => { const pageTitle = getPageTitleFromPath(pathname); return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; From 316b80ed78ac2843f806f0e0f831576f0415cb09 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sat, 5 Oct 2024 17:10:04 +0900 Subject: [PATCH 16/16] chore: update ProfilePictureUploader.tsx (#7440) occured -> occurred --- .../settings/profile/components/ProfilePictureUploader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index dcfcfaf5e7e1..6d9bf03e00e2 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -71,7 +71,7 @@ export const ProfilePictureUploader = () => { return result; } catch (error) { - setErrorMessage('An error occured while uploading the picture.'); + setErrorMessage('An error occurred while uploading the picture.'); } }; @@ -97,7 +97,7 @@ export const ProfilePictureUploader = () => { setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: null }); } catch (error) { - setErrorMessage('An error occured while removing the picture.'); + setErrorMessage('An error occurred while removing the picture.'); } };