Reduce relation loading overload on FE graphql queries (#14991)

## Problem

With Twenty usage growing, we are facing challenges on server side to
respond to the demand. The current bottleneck we are facing is server
CPU.
While investigating CPU performances, we figured out that loading all
relation fields was the biggest issue.

### About graphql query response size

**Example:** on `People Index` Table page:
we query: `person.company` and we query **all non relation fields** on
company relation field.
`{ person { company { id, name, domainName, employees, address ... } }`
 
 
However we only need **company.id, company.name and company.domainName**
to be able to render the Company Chip in the company column (RELATION)
in the table.
`{ person { company { id, name, domainName } }`

We initially assumed that the querying all non relation fields was not
an issue because it was not adding additional load on database (which is
also partially true: if a field is containing a huge json this will add
load on postgres as data needs to be transfered)
This assumption is wrong on CPU side:
- when loading one to many relations (company.people), this quickly ads
up and we end up with response of 20kB quite quickly, even without
adding any custom field on person.
- even when loading a many to one relation, if the user is storing big
data in a given field (note.body for instance, or workflowRun.state),
this starts also being an issue.
- Worst case scenario are starting to happen: on a production workspace
with 20 active workflows. Loading workflow table while displaying
workflow runs commands (20 workflows x 60 workflowRuns x
workflowRun.state which is a big JSON) result in a response of... 125MB.

### Why is it an issue?

When yoga (graphql engine) parses a response to send it back in server
response, it's using `JSON.parse `(or stringify depending on the case).
Parsing data is CPU instensive (as you need to validate, transform) and
this is more or less `O(n)`.
This means returning 125MB is very intense on the CPU and will likely
use 100% of the CPU for ~1sec in our production servers.

NodeJs (nodeV8) is single threaded. It's able to process multiple
requests in parallel but to do that, it will cut them in "microTasks"
and process each microTasks (that can belong to different requests) one
after the other. Exactly like a single threaded CPU would allocate some
time to a process and then to the next one, etc.

This means that this big response request will actually block the other
request.
As a result, all requests are slow and our infrastructure is multi
tenant so some customers are impacting others.

This is even a bigger issue when our health checks start to fail and
containers are starting being considered as unhealthy and killed by the
orchestrator


## How to fix this

1. On Front end side, we should only query what we need. In this PR, I'm
forcing the relations to only query: `id`, `labelIdentifier`,
`imageIdentifier`
2. (later) as we are API first, we also need to do something to protect
the servers from this side too. To do that, Graphql APIs can associate a
cost to each graphql field (maybe 1 for a test fixed, 2 for a JSON
field, 10 for relations, etc...) and we can throttle based on that.

## Changes in this PR

It's mainly about refactor the tooling to generate `RecordGqlFields`
(`generateDepthOneGqlFields`). this tooling is used to generate the list
of fields we want to query. Most of the time we want to query a record
with one level of nesting.
1) Refactor to remove duplicate code => `generateDepthOne` become
`generateDepth` and can handle both `depth = 0 | 1`

2) Introduce `generateDepthRecordGqlFieldsFromFields.ts` which is the
base and can give you a list of gqlFields based on FieldMetadataItems

3) Introduce `generateDepthRecordGqlFieldsFromObject` which is a
shortcut for an object
4) Introduce `generateDepthRecordGqlFieldsFromRecords` which is the
intersection between `generateDepthRecordGqlFieldsFromObject` and a
given record (useful for cache tooling)
5) Replace all usages + introduce a hook
`useGenerateDepthRecordGqlFieldsFromObject` to ease usage
This commit is contained in:
Charles Bochet
2025-10-10 14:31:12 +02:00
committed by GitHub
parent e235e5f9d7
commit 37473175a0
74 changed files with 1024 additions and 2955 deletions
@@ -2,40 +2,45 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
export const fetchAllThreadMessagesOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({ messageThreadId }: { messageThreadId: string }) => ({
objectNameSingular: CoreObjectNameSingular.Message,
variables: {
filter: {
messageThreadId: {
eq: messageThreadId || '',
},
type FetchAllThreadMessagesOperationSignatureFactory = {
messageThreadId: string | null;
};
export const fetchAllThreadMessagesOperationSignatureFactory: RecordGqlOperationSignatureFactory<
FetchAllThreadMessagesOperationSignatureFactory
> = ({ messageThreadId }: FetchAllThreadMessagesOperationSignatureFactory) => ({
objectNameSingular: CoreObjectNameSingular.Message,
variables: {
filter: {
messageThreadId: {
eq: messageThreadId || '',
},
orderBy: [
{
receivedAt: 'AscNullsLast',
},
],
limit: QUERY_MAX_RECORDS,
},
fields: {
orderBy: [
{
receivedAt: 'AscNullsLast',
},
],
limit: QUERY_MAX_RECORDS,
},
fields: {
id: true,
__typename: true,
createdAt: true,
headerMessageId: true,
subject: true,
text: true,
receivedAt: true,
messageThread: {
id: true,
__typename: true,
createdAt: true,
headerMessageId: true,
subject: true,
text: true,
receivedAt: true,
messageThread: {
id: true,
},
messageParticipants: {
id: true,
role: true,
displayName: true,
handle: true,
person: true,
workspaceMember: true,
},
},
});
messageParticipants: {
id: true,
role: true,
displayName: true,
handle: true,
person: true,
workspaceMember: true,
},
},
});
@@ -1,40 +1,45 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const createOneActivityOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({ objectNameSingular }: { objectNameSingular: CoreObjectNameSingular }) =>
objectNameSingular === CoreObjectNameSingular.Note
? {
objectNameSingular: CoreObjectNameSingular.Note,
variables: {},
fields: {
type CreateOneActivityOperationSignatureFactory = {
objectNameSingular: CoreObjectNameSingular;
};
export const createOneActivityOperationSignatureFactory: RecordGqlOperationSignatureFactory<
CreateOneActivityOperationSignatureFactory
> = ({ objectNameSingular }: CreateOneActivityOperationSignatureFactory) =>
objectNameSingular === CoreObjectNameSingular.Note
? {
objectNameSingular: CoreObjectNameSingular.Note,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
attachments: true,
bodyV2: true,
title: true,
},
}
: {
objectNameSingular: CoreObjectNameSingular.Task,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
createdAt: true,
updatedAt: true,
attachments: true,
bodyV2: true,
title: true,
},
}
: {
objectNameSingular: CoreObjectNameSingular.Task,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
},
attachments: true,
bodyV2: true,
title: true,
status: true,
dueAt: true,
},
};
attachments: true,
bodyV2: true,
title: true,
status: true,
dueAt: true,
},
};
@@ -3,72 +3,75 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({
objectMetadataItems,
objectNameSingular,
}: {
objectMetadataItems: ObjectMetadataItem[];
objectNameSingular: CoreObjectNameSingular;
}) => {
const body = {
bodyV2: {
markdown: true,
blocknote: true,
},
};
type FindActivitiesOperationSignatureFactory = {
objectMetadataItems: ObjectMetadataItem[];
objectNameSingular: CoreObjectNameSingular;
};
return {
objectNameSingular: objectNameSingular,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
author: {
id: true,
name: true,
__typename: true,
},
authorId: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
},
comments: true,
attachments: true,
...body,
title: true,
status: true,
dueAt: true,
reminderAt: true,
type: true,
...(objectNameSingular === CoreObjectNameSingular.Note
? {
noteTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
note: true,
noteId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {
taskTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
task: true,
taskId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}),
},
};
export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatureFactory<
FindActivitiesOperationSignatureFactory
> = ({
objectMetadataItems,
objectNameSingular,
}: FindActivitiesOperationSignatureFactory) => {
const body = {
bodyV2: {
markdown: true,
blocknote: true,
},
};
return {
objectNameSingular: objectNameSingular,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
author: {
id: true,
name: true,
__typename: true,
},
authorId: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
},
comments: true,
attachments: true,
...body,
title: true,
status: true,
dueAt: true,
reminderAt: true,
type: true,
...(objectNameSingular === CoreObjectNameSingular.Note
? {
noteTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
note: true,
noteId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {
taskTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
task: true,
taskId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}),
},
};
};
@@ -4,21 +4,24 @@ import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectN
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({
objectNameSingular,
objectMetadataItems,
}: {
objectNameSingular: CoreObjectNameSingular;
objectMetadataItems: ObjectMetadataItem[];
}) => ({
objectNameSingular: getJoinObjectNameSingular(objectNameSingular),
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
});
type FindActivityTargetsOperationSignatureFactory = {
objectNameSingular: CoreObjectNameSingular;
objectMetadataItems: ObjectMetadataItem[];
};
export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSignatureFactory<
FindActivityTargetsOperationSignatureFactory
> = ({
objectNameSingular,
objectMetadataItems,
}: FindActivityTargetsOperationSignatureFactory) => ({
objectNameSingular: getJoinObjectNameSingular(objectNameSingular),
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
});
@@ -1,10 +1,12 @@
import { type MockedResponse } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import gql from 'graphql-tag';
import { useCompleteTask } from '@/activities/tasks/hooks/useCompleteTask';
import { type Task } from '@/activities/types/Task';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const task: Task = {
id: '123',
@@ -23,151 +25,17 @@ const task: Task = {
__typename: 'Task',
};
const updateOneTaskMutation = generateUpdateOneRecordMutation({
objectMetadataItem: getMockObjectMetadataItemOrThrow('task'),
objectMetadataItems: generatedMockObjectMetadataItems,
computeReferences: false,
objectPermissionsByObjectMetadataId: {},
});
const mocks: MockedResponse[] = [
{
request: {
query: gql`
mutation UpdateOneTask($idToUpdate: UUID!, $input: TaskUpdateInput!) {
updateTask(id: $idToUpdate, data: $input) {
__typename
assignee {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
assigneeId
attachments {
edges {
node {
__typename
authorId
companyId
createdAt
deletedAt
fullPath
id
name
noteId
opportunityId
personId
petId
rocketId
surveyResultId
taskId
type
updatedAt
}
}
}
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
dueAt
favorites {
edges {
node {
__typename
companyId
createdAt
deletedAt
favoriteFolderId
forWorkspaceMemberId
id
noteId
opportunityId
personId
petId
position
rocketId
surveyResultId
taskId
updatedAt
viewId
workflowId
workflowRunId
workflowVersionId
}
}
}
id
position
status
taskTargets {
edges {
node {
__typename
companyId
createdAt
deletedAt
id
opportunityId
personId
petId
rocketId
surveyResultId
taskId
updatedAt
}
}
}
timelineActivities {
edges {
node {
__typename
companyId
createdAt
deletedAt
happensAt
id
linkedObjectMetadataId
linkedRecordCachedName
linkedRecordId
name
noteId
opportunityId
personId
petId
properties
rocketId
surveyResultId
taskId
updatedAt
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}
}
title
updatedAt
}
}
`,
query: updateOneTaskMutation,
variables: {
idToUpdate: task.id,
input: { status: task.status },
@@ -2,9 +2,8 @@ import { useLinkedObjectsTitle } from '@/activities/timeline-activities/hooks/us
import { type TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { isDefined } from 'twenty-shared/utils';
@@ -16,9 +15,11 @@ export const useTimelineActivities = (
nameSingular: targetableObject.targetObjectNameSingular,
});
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
});
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
depth: 1,
});
const {
records: timelineActivities,
@@ -36,7 +37,7 @@ export const useTimelineActivities = (
createdAt: 'DescNullsFirst',
},
],
recordGqlFields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
recordGqlFields: depthOneRecordGqlFields,
fetchPolicy: 'cache-and-network',
});
@@ -1,8 +1,12 @@
import { gql } from '@apollo/client';
import { Favorite } from '@/favorites/types/Favorite';
import { generateCreateOneRecordMutation } from '@/object-metadata/utils/generateCreateOneRecordMutation';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { AvatarType } from 'twenty-ui/display';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
export const mockId = '8f3b2121-f194-4ba4-9fbf-2d5a37126806';
export const favoriteId = 'f088c8c9-05d2-4276-b065-b863cc7d0b33';
@@ -101,788 +105,30 @@ export const sortedFavorites = [
},
];
const UPDATE_ONE_FAVORITE_MUTATION = gql`
mutation UpdateOneFavorite($idToUpdate: UUID!, $input: FavoriteUpdateInput!) {
updateFavorite(id: $idToUpdate, data: $input) {
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
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
favoriteFolder {
__typename
createdAt
deletedAt
id
name
position
updatedAt
}
favoriteFolderId
forWorkspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
forWorkspaceMemberId
id
note {
__typename
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
pet {
__typename
age
averageCostOfKibblePerMonth {
amountMicros
currencyCode
}
bio
birthday
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
extraData
id
interestingFacts
isGoodWithKids
location {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
makesOwnerThinkOf {
firstName
lastName
}
name
pictures {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
position
soundSwag
species
traits
updatedAt
vetEmail {
primaryEmail
additionalEmails
}
vetPhone {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
}
petId
position
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
position
updatedAt
}
rocketId
surveyResult {
__typename
averageEstimatedNumberOfAtomsInTheUniverse
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
participants
percentageOfCompletion
position
score
shortNotes
updatedAt
}
surveyResultId
task {
__typename
assigneeId
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
view {
__typename
createdAt
deletedAt
icon
id
isCompact
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
kanbanFieldMetadataId
key
name
objectMetadataId
openRecordIn
position
type
updatedAt
}
viewId
workflow {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
lastPublishedVersionId
name
position
statuses
updatedAt
}
workflowId
workflowRun {
__typename
context
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
endedAt
id
name
output
position
startedAt
status
updatedAt
workflowId
workflowVersionId
}
workflowRunId
workflowVersion {
__typename
createdAt
deletedAt
id
name
position
status
steps
trigger
updatedAt
workflowId
}
workflowVersionId
}
}
`;
const favoriteObjectMetadataItem = getMockObjectMetadataItemOrThrow('favorite')
const UPDATE_ONE_FAVORITE_MUTATION = generateUpdateOneRecordMutation(
{
objectMetadataItem: favoriteObjectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
computeReferences: false,
objectPermissionsByObjectMetadataId: {},
},
)
const CREATE_ONE_FAVORITE_MUTATION = generateCreateOneRecordMutation(
{
objectMetadataItem: favoriteObjectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
objectPermissionsByObjectMetadataId: {},
},
)
export const mocks = [
{
request: {
query: gql`
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
createFavorite(data: $input) {
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
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
favoriteFolder {
__typename
createdAt
deletedAt
id
name
position
updatedAt
}
favoriteFolderId
forWorkspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
forWorkspaceMemberId
id
note {
__typename
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
pet {
__typename
age
averageCostOfKibblePerMonth {
amountMicros
currencyCode
}
bio
birthday
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
extraData
id
interestingFacts
isGoodWithKids
location {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
makesOwnerThinkOf {
firstName
lastName
}
name
pictures {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
position
soundSwag
species
traits
updatedAt
vetEmail {
primaryEmail
additionalEmails
}
vetPhone {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
}
petId
position
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
position
updatedAt
}
rocketId
surveyResult {
__typename
averageEstimatedNumberOfAtomsInTheUniverse
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
participants
percentageOfCompletion
position
score
shortNotes
updatedAt
}
surveyResultId
task {
__typename
assigneeId
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
view {
__typename
createdAt
deletedAt
icon
id
isCompact
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
kanbanFieldMetadataId
key
name
objectMetadataId
openRecordIn
position
type
updatedAt
}
viewId
workflow {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
lastPublishedVersionId
name
position
statuses
updatedAt
}
workflowId
workflowRun {
__typename
context
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
endedAt
id
name
output
position
startedAt
status
updatedAt
workflowId
workflowVersionId
}
workflowRunId
workflowVersion {
__typename
createdAt
deletedAt
id
name
position
status
steps
trigger
updatedAt
workflowId
}
workflowVersionId
}
}
`,
query: CREATE_ONE_FAVORITE_MUTATION,
variables: {
input: {
personId: favoriteTargetObjectId,
@@ -0,0 +1,50 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
import { gql } from '@apollo/client';
import { type ObjectPermissions } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
export const generateCreateOneRecordMutation = ({
objectMetadataItem,
objectMetadataItems,
recordGqlFields,
objectPermissionsByObjectMetadataId,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
recordGqlFields?: RecordGqlOperationGqlRecordFields;
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
}) => {
const appliedRecordGqlFields =
recordGqlFields ??
generateDepthRecordGqlFieldsFromObject({
depth: 1,
objectMetadataItems,
objectMetadataItem,
});
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const mutationResponseField = getCreateOneRecordMutationResponseField(
objectMetadataItem.nameSingular,
);
const createOneRecordMutation = gql`
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
${mutationResponseField}(data: $input) ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
recordGqlFields: appliedRecordGqlFields,
objectPermissionsByObjectMetadataId,
})}
}
`;
return createOneRecordMutation;
};
@@ -1,7 +1,7 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
import { gql } from '@apollo/client';
import { type ObjectPermissions } from 'twenty-shared/types';
@@ -25,7 +25,9 @@ export const generateUpdateOneRecordMutation = ({
}) => {
const appliedRecordGqlFields =
recordGqlFields ??
generateDepthOneRecordGqlFields({
generateDepthRecordGqlFieldsFromObject({
depth: 1,
objectMetadataItems,
objectMetadataItem,
});
@@ -0,0 +1,16 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isImageIdentifierField } from '@/object-metadata/utils/isImageIdentifierField';
export const getImageIdentifierFieldMetadataItem = (
objectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'imageIdentifierFieldMetadataId' | 'nameSingular'
>,
): FieldMetadataItem | undefined =>
objectMetadataItem.fields.find((fieldMetadataItem) =>
isImageIdentifierField({
fieldMetadataItem,
objectMetadataItem,
}),
);
@@ -0,0 +1,25 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const isImageIdentifierField = ({
fieldMetadataItem,
objectMetadataItem,
}: {
fieldMetadataItem: Pick<FieldMetadataItem, 'id' | 'name'>;
objectMetadataItem: Pick<
ObjectMetadataItem,
'imageIdentifierFieldMetadataId' | 'nameSingular'
>;
}) => {
if (
objectMetadataItem.nameSingular === CoreObjectNameSingular.Company &&
fieldMetadataItem.name === 'domainName'
) {
return true;
}
return (
fieldMetadataItem.id === objectMetadataItem.imageIdentifierFieldMetadataId
);
};
@@ -3,7 +3,7 @@ import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
import { type ObjectPermissions } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
@@ -2,7 +2,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
import { FieldMetadataType, type ObjectPermissions } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
@@ -1,13 +1,12 @@
import gql from 'graphql-tag';
import { useRecoilValue } from 'recoil';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { prefillRecord } from '@/object-record/utils/prefillRecord';
@@ -21,7 +20,7 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: objectMetadataItem.nameSingular,
});
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const apolloCoreClient = useApolloCoreClient();
@@ -31,9 +30,11 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
objectMetadataItem,
input: record,
});
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItems,
objectMetadataItem,
record: prefilledRecord,
depth: 1,
});
const fragment = gql`
fragment Create${capitalize(
@@ -1,12 +1,11 @@
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -20,13 +19,18 @@ export const useGetRecordFromCache = ({
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { objectMetadataItems } = useObjectMetadataItems();
const appliedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const appliedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const apolloCoreClient = useApolloCoreClient();
return useCallback(
@@ -3,8 +3,8 @@ import { type ApolloCache, gql } from '@apollo/client';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
@@ -35,7 +35,12 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
}
const appliedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
recordGqlFields ??
generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
depth: 1,
objectMetadataItems,
});
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
@@ -4,7 +4,7 @@ import gql from 'graphql-tag';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
@@ -0,0 +1,30 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
export type GenerateDepthRecordGqlFieldsFromObjectData = {
objectNameSingular: string;
depth: 0 | 1;
shouldOnlyLoadRelationIdentifiers?: boolean;
};
export const useGenerateDepthRecordGqlFieldsFromObject = ({
objectNameSingular,
depth,
shouldOnlyLoadRelationIdentifiers = true,
}: GenerateDepthRecordGqlFieldsFromObjectData) => {
const { objectMetadataItems } = useObjectMetadataItems();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
return {
recordGqlFields: generateDepthRecordGqlFieldsFromObject({
objectMetadataItems,
objectMetadataItem,
depth,
shouldOnlyLoadRelationIdentifiers,
}),
};
};
@@ -0,0 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record gql fields from object 1`] = `
{
"accountOwner": {
"avatarUrl": true,
"id": true,
"name": true,
},
"accountOwnerId": true,
"address": true,
"annualRecurringRevenue": true,
"attachments": {
"id": true,
"name": true,
},
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"domainName": true,
"employees": true,
"favorites": {
"id": true,
},
"id": true,
"idealCustomerProfile": true,
"introVideo": true,
"linkedinLink": true,
"name": true,
"noteTargets": {
"id": true,
},
"opportunities": {
"id": true,
"name": true,
},
"people": {
"avatarUrl": true,
"id": true,
"name": true,
},
"position": true,
"searchVector": true,
"tagline": true,
"taskTargets": {
"id": true,
},
"timelineActivities": {
"id": true,
},
"updatedAt": true,
"visaSponsorship": true,
"workPolicy": true,
"xLink": true,
}
`;
exports[`generateDepthRecordGqlFieldsFromObject should generate depth zero record gql fields from object 1`] = `
{
"accountOwnerId": true,
"address": true,
"annualRecurringRevenue": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"domainName": true,
"employees": true,
"id": true,
"idealCustomerProfile": true,
"introVideo": true,
"linkedinLink": true,
"name": true,
"position": true,
"searchVector": true,
"tagline": true,
"updatedAt": true,
"visaSponsorship": true,
"workPolicy": true,
"xLink": true,
}
`;
@@ -0,0 +1,25 @@
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
describe('generateDepthRecordGqlFieldsFromObject', () => {
it('should generate depth one record gql fields from object', () => {
const result = generateDepthRecordGqlFieldsFromObject({
objectMetadataItem: getMockObjectMetadataItemOrThrow('company'),
objectMetadataItems: generatedMockObjectMetadataItems,
depth: 1,
});
expect(result).toMatchSnapshot();
});
it('should generate depth zero record gql fields from object', () => {
const result = generateDepthRecordGqlFieldsFromObject({
objectMetadataItem: getMockObjectMetadataItemOrThrow('company'),
objectMetadataItems: generatedMockObjectMetadataItems,
depth: 0,
});
expect(result).toMatchSnapshot();
});
});
@@ -0,0 +1,128 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getImageIdentifierFieldMetadataItem } from '@/object-metadata/utils/getImageIdentifierFieldMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
export type GenerateDepthRecordGqlFieldsFromFields = {
objectMetadataItems: ObjectMetadataItem[];
fields: Pick<
FieldMetadataItem,
'name' | 'type' | 'settings' | 'morphRelations' | 'relation'
>[];
depth: 0 | 1;
shouldOnlyLoadRelationIdentifiers?: boolean;
};
export const generateDepthRecordGqlFieldsFromFields = ({
objectMetadataItems,
fields,
depth,
shouldOnlyLoadRelationIdentifiers = true,
}: GenerateDepthRecordGqlFieldsFromFields) => {
const generatedRecordGqlFields: RecordGqlFields = fields.reduce(
(recordGqlFields, fieldMetadata) => {
const isMorphRelation =
fieldMetadata.type === FieldMetadataType.MORPH_RELATION;
const isRelation = fieldMetadata.type === FieldMetadataType.RELATION;
const relationType = fieldMetadata.settings?.relationType;
if (isRelation) {
const targetObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
fieldMetadata.relation?.targetObjectMetadata.id,
);
if (!targetObjectMetadataItem) {
throw new Error(
`Target object metadata item not found for ${fieldMetadata.name}`,
);
}
const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(targetObjectMetadataItem);
const imageIdentifierFieldMetadataItem =
getImageIdentifierFieldMetadataItem(targetObjectMetadataItem);
const relationIdentifierSubGqlFields = {
id: true,
...(isDefined(labelIdentifierFieldMetadataItem)
? { [labelIdentifierFieldMetadataItem.name]: true }
: {}),
...(isDefined(imageIdentifierFieldMetadataItem)
? { [imageIdentifierFieldMetadataItem.name]: true }
: {}),
};
const manyToOneGqlFields = {
[`${fieldMetadata.name}Id`]: true,
};
return {
...recordGqlFields,
...(depth === 1 && shouldOnlyLoadRelationIdentifiers
? { [fieldMetadata.name]: relationIdentifierSubGqlFields }
: undefined),
...(depth === 1 && !shouldOnlyLoadRelationIdentifiers
? { [fieldMetadata.name]: true }
: undefined),
...(relationType === RelationType.MANY_TO_ONE
? manyToOneGqlFields
: undefined),
};
}
if (isMorphRelation) {
if (!isDefined(fieldMetadata.morphRelations)) {
throw new Error(
`Field ${fieldMetadata.name} is missing, please refresh the page. If the problem persists, please contact support.`,
);
}
const morphGqlFields = fieldMetadata.morphRelations.map(
(morphRelation) => ({
gqlField: computeMorphRelationFieldName({
fieldName: fieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
}),
fieldMetadata,
}),
);
return {
...recordGqlFields,
...morphGqlFields.reduce(
(morphGqlFields, morphGqlField) => ({
...morphGqlFields,
...(depth === 1
? { [`${morphGqlField.gqlField}`]: { id: true, name: true } }
: {}),
...(relationType === RelationType.MANY_TO_ONE
? { [`${morphGqlField.gqlField}Id`]: true }
: {}),
}),
{},
),
};
}
return {
...recordGqlFields,
[`${fieldMetadata.name}`]: true,
};
},
{},
);
return generatedRecordGqlFields;
};
@@ -0,0 +1,23 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields';
export type GenerateDepthRecordGqlFields = {
objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: ObjectMetadataItem;
depth: 0 | 1;
shouldOnlyLoadRelationIdentifiers?: boolean;
};
export const generateDepthRecordGqlFieldsFromObject = ({
objectMetadataItems,
objectMetadataItem,
depth,
shouldOnlyLoadRelationIdentifiers = true,
}: GenerateDepthRecordGqlFields) => {
return generateDepthRecordGqlFieldsFromFields({
objectMetadataItems,
fields: objectMetadataItem.fields,
depth,
shouldOnlyLoadRelationIdentifiers,
});
};
@@ -0,0 +1,34 @@
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import {
generateDepthRecordGqlFieldsFromObject,
type GenerateDepthRecordGqlFields,
} from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
type ComputeDepthRecordGqlFieldsFromRecordArgs =
GenerateDepthRecordGqlFields & {
record: Partial<ObjectRecord>;
};
export const generateDepthRecordGqlFieldsFromRecord = ({
objectMetadataItem,
objectMetadataItems,
depth,
record,
}: ComputeDepthRecordGqlFieldsFromRecordArgs): RecordGqlFields => {
const depthRecordGqlFields = generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth,
});
const recordKeys = Object.keys(record);
return Object.keys(depthRecordGqlFields).reduce<RecordGqlFields>(
(acc, key) => {
return {
...acc,
[key]: recordKeys.includes(key),
};
},
depthRecordGqlFields,
);
};
@@ -1,5 +1,5 @@
import { type RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
export type RecordGqlOperationSignatureFactory = (
factoryParams: any,
export type RecordGqlOperationSignatureFactory<FactoryParams extends object> = (
factoryParams: FactoryParams,
) => RecordGqlOperationSignature;
@@ -1,49 +0,0 @@
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import {
allMockPersonRecords,
getMockPersonObjectMetadataItem,
} from '~/testing/mock-data/people';
describe('computeDepthOneRecordGqlFieldsFromRecord', () => {
const objectMetadataItem = getMockPersonObjectMetadataItem();
it('Should handle basic call', () => {
const personRecord = allMockPersonRecords[0];
const result = computeDepthOneRecordGqlFieldsFromRecord({
objectMetadataItem,
record: personRecord,
});
expect(result).toMatchInlineSnapshot(`
{
"attachments": false,
"avatarUrl": true,
"calendarEventParticipants": false,
"city": true,
"company": true,
"companyId": false,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"emails": true,
"favorites": false,
"id": true,
"intro": true,
"jobTitle": true,
"linkedinLink": true,
"messageParticipants": false,
"name": true,
"noteTargets": true,
"performanceRating": true,
"phones": true,
"pointOfContactForOpportunities": false,
"position": true,
"searchVector": false,
"taskTargets": true,
"timelineActivities": false,
"updatedAt": true,
"whatsapp": true,
"workPreference": true,
"xLink": true,
}
`);
});
});
@@ -1,44 +0,0 @@
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people';
describe('generateDepthOneRecordGqlFields', () => {
const objectMetadataItem = getMockPersonObjectMetadataItem();
it('Should handle basic call with standalone objectMetadataItem', () => {
const result = generateDepthOneRecordGqlFields({
objectMetadataItem,
});
expect(result).toMatchInlineSnapshot(`
{
"attachments": true,
"avatarUrl": true,
"calendarEventParticipants": true,
"city": true,
"company": true,
"companyId": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"emails": true,
"favorites": true,
"id": true,
"intro": true,
"jobTitle": true,
"linkedinLink": true,
"messageParticipants": true,
"name": true,
"noteTargets": true,
"performanceRating": true,
"phones": true,
"pointOfContactForOpportunities": true,
"position": true,
"searchVector": true,
"taskTargets": true,
"timelineActivities": true,
"updatedAt": true,
"whatsapp": true,
"workPreference": true,
"xLink": true,
}
`);
});
});
@@ -1,34 +0,0 @@
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people';
describe('generateDepthOneWithoutRelationsRecordGqlFields', () => {
const objectMetadataItem = getMockPersonObjectMetadataItem();
it('Should handle basic call with standalone objectMetadataItem', () => {
const result = generateDepthOneWithoutRelationsRecordGqlFields({
objectMetadataItem,
});
expect(result).toMatchInlineSnapshot(`
{
"avatarUrl": true,
"city": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"emails": true,
"id": true,
"intro": true,
"jobTitle": true,
"linkedinLink": true,
"name": true,
"performanceRating": true,
"phones": true,
"position": true,
"searchVector": true,
"updatedAt": true,
"whatsapp": true,
"workPreference": true,
"xLink": true,
}
`);
});
});
@@ -1,30 +0,0 @@
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import {
type GenerateDepthOneRecordGqlFields,
generateDepthOneRecordGqlFields,
} from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
type ComputeDepthOneRecordGqlFieldsFromRecordArgs =
GenerateDepthOneRecordGqlFields & {
record: Partial<ObjectRecord>;
};
export const computeDepthOneRecordGqlFieldsFromRecord = ({
objectMetadataItem,
record,
}: ComputeDepthOneRecordGqlFieldsFromRecordArgs): RecordGqlFields => {
const depthOneRecordGqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem,
});
const recordKeys = Object.keys(record);
return Object.keys(depthOneRecordGqlFields).reduce<RecordGqlFields>(
(acc, key) => {
return {
...acc,
[key]: recordKeys.includes(key),
};
},
depthOneRecordGqlFields,
);
};
@@ -1,73 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
export type GenerateDepthOneRecordGqlFields = {
objectMetadataItem: ObjectMetadataItem;
};
export const generateDepthOneRecordGqlFields = ({
objectMetadataItem,
}: GenerateDepthOneRecordGqlFields) => {
const gqlFields = objectMetadataItem.readableFields.flatMap(
(fieldMetadata) => {
const isManyToOneMorphRelation =
fieldMetadata.type === FieldMetadataType.MORPH_RELATION &&
fieldMetadata.settings.relationType === RelationType.MANY_TO_ONE;
const isManyToOneRelation =
fieldMetadata.type === FieldMetadataType.RELATION &&
fieldMetadata.settings.relationType === RelationType.MANY_TO_ONE;
if (isManyToOneRelation) {
return [`${fieldMetadata.name}Id`, `${fieldMetadata.name}`];
}
if (isManyToOneMorphRelation) {
if (!isDefined(fieldMetadata.morphRelations)) {
throw new Error(
`Field ${fieldMetadata.name} is missing, please refresh the page. If the problem persists, please contact support.`,
);
}
const morphGqlFields = fieldMetadata.morphRelations.map(
(morphRelation) => ({
gqlField: computeMorphRelationFieldName({
fieldName: fieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
}),
fieldMetadata,
}),
);
return morphGqlFields.flatMap((morphGqlField) => [
morphGqlField.gqlField,
`${morphGqlField.gqlField}Id`,
]);
}
return [fieldMetadata.name];
},
);
return gqlFields.reduce<RecordGqlFields>((acc, field) => {
return {
...acc,
[field]:
// TODO: Remove once we have made the workflows lighter
(objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow ||
objectMetadataItem.nameSingular ===
CoreObjectNameSingular.WorkflowVersion ||
objectMetadataItem.nameSingular ===
CoreObjectNameSingular.WorkflowRun) &&
(field === 'versions' || field === 'runs')
? { id: true, name: true }
: true,
};
}, {});
};
@@ -1,23 +0,0 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type GenerateDepthOneWithoutRelationsRecordGqlFields = {
objectMetadataItem: ObjectMetadataItem;
};
export const generateDepthOneWithoutRelationsRecordGqlFields = ({
objectMetadataItem,
}: GenerateDepthOneWithoutRelationsRecordGqlFields) => {
return objectMetadataItem.fields
.filter(
(field) =>
field.type !== FieldMetadataType.RELATION &&
field.type !== FieldMetadataType.MORPH_RELATION,
)
.reduce<Record<string, true>>((acc, field) => {
return {
...acc,
[field.name]: true,
};
}, {});
};
@@ -1,4 +1,4 @@
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { isDefined } from 'twenty-shared/utils';
export const isRecordGqlFieldsNode = (
@@ -56,22 +56,8 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
authorId
companyId
createdAt
deletedAt
fullPath
id
name
noteId
opportunityId
personId
petId
rocketId
surveyResultId
taskId
type
updatedAt
}
}
}
@@ -80,75 +66,21 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
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
context
}
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
@@ -167,25 +99,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
companyId
createdAt
deletedAt
favoriteFolderId
forWorkspaceMemberId
id
noteId
opportunityId
personId
petId
position
rocketId
surveyResultId
taskId
updatedAt
viewId
workflowId
workflowRunId
workflowVersionId
}
}
}
@@ -201,16 +115,8 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
createdAt
deletedAt
displayName
handle
id
messageId
personId
role
updatedAt
workspaceMemberId
}
}
}
@@ -222,17 +128,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
companyId
createdAt
deletedAt
id
noteId
opportunityId
personId
petId
rocketId
surveyResultId
updatedAt
}
}
}
@@ -247,26 +143,8 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
}
}
@@ -275,17 +153,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
companyId
createdAt
deletedAt
id
opportunityId
personId
petId
rocketId
surveyResultId
taskId
updatedAt
}
}
}
@@ -293,28 +161,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
edges {
node {
__typename
companyId
createdAt
deletedAt
happensAt
id
linkedObjectMetadataId
linkedRecordCachedName
linkedRecordId
name
noteId
opportunityId
personId
petId
properties
rocketId
surveyResultId
taskId
updatedAt
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}
}
@@ -15,42 +15,14 @@ exports[`useDeleteOneRecord B. Starting from filled cache 1. Should handle succe
"city": "ASd",
"company": {
"__typename": "Company",
"address": {
"__typename": "Address",
"addressCity": "Dublin",
"addressCountry": "Ireland",
"addressLat": null,
"addressLng": null,
"addressPostcode": null,
"addressState": null,
"addressStreet1": "Eutaw Street",
"addressStreet2": null,
},
"createdAt": "2025-02-16T08:21:51.715Z",
"createdBy": {
"__typename": "Actor",
"context": {},
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"domainName": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "https://linkedin.com",
"secondaryLinks": [],
},
"employees": null,
"id": "20202020-3ec3-4fe3-8997-b76aa0bfa408",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": "Linkedin",
"position": 1,
},
"createdAt": "2025-01-02T09:52:46.814Z",
"createdBy": {
@@ -153,94 +125,21 @@ exports[`useDeleteOneRecord B. Starting from filled cache 1. Should handle succe
{
"__typename": "Person",
"avatarUrl": null,
"city": "Seattle",
"createdAt": "2025-01-01T09:50:00.000Z",
"createdBy": {
"__typename": "Actor",
"context": null,
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"emails": {
"additionalEmails": [],
"primaryEmail": "christoph.calisto@linkedin.com",
},
"id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5",
"intro": "",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Christoph",
"lastName": "Callisto",
},
"performanceRating": null,
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234562",
},
"position": 1,
"updatedAt": "2025-01-01T09:50:00.000Z",
"whatsapp": {
"additionalPhones": [],
"primaryPhoneCallingCode": "",
"primaryPhoneCountryCode": "",
"primaryPhoneNumber": "",
},
"workPreference": null,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
{
"__typename": "Person",
"city": "Los Angeles",
"createdAt": "2025-01-02T09:48:36.193Z",
"createdBy": {
"__typename": "Actor",
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"id": "20202020-ac73-4797-824e-87a1f5aea9e0",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Sylvie",
"lastName": "Palmer",
},
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234576",
},
"position": 2,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
],
"position": 1,
@@ -255,42 +154,14 @@ exports[`useDeleteOneRecord B. Starting from filled cache 2. Should handle optim
"city": "ASd",
"company": {
"__typename": "Company",
"address": {
"__typename": "Address",
"addressCity": "Dublin",
"addressCountry": "Ireland",
"addressLat": null,
"addressLng": null,
"addressPostcode": null,
"addressState": null,
"addressStreet1": "Eutaw Street",
"addressStreet2": null,
},
"createdAt": "2025-02-16T08:21:51.715Z",
"createdBy": {
"__typename": "Actor",
"context": {},
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"domainName": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "https://linkedin.com",
"secondaryLinks": [],
},
"employees": null,
"id": "20202020-3ec3-4fe3-8997-b76aa0bfa408",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": "Linkedin",
"position": 1,
},
"createdAt": "2025-01-02T09:52:46.814Z",
"createdBy": {
@@ -393,94 +264,21 @@ exports[`useDeleteOneRecord B. Starting from filled cache 2. Should handle optim
{
"__typename": "Person",
"avatarUrl": null,
"city": "Seattle",
"createdAt": "2025-01-01T09:50:00.000Z",
"createdBy": {
"__typename": "Actor",
"context": null,
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"emails": {
"additionalEmails": [],
"primaryEmail": "christoph.calisto@linkedin.com",
},
"id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5",
"intro": "",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Christoph",
"lastName": "Callisto",
},
"performanceRating": null,
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234562",
},
"position": 1,
"updatedAt": "2025-01-01T09:50:00.000Z",
"whatsapp": {
"additionalPhones": [],
"primaryPhoneCallingCode": "",
"primaryPhoneCountryCode": "",
"primaryPhoneNumber": "",
},
"workPreference": null,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
{
"__typename": "Person",
"city": "Los Angeles",
"createdAt": "2025-01-02T09:48:36.193Z",
"createdBy": {
"__typename": "Actor",
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"id": "20202020-ac73-4797-824e-87a1f5aea9e0",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Sylvie",
"lastName": "Palmer",
},
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234576",
},
"position": 2,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
],
"position": 1,
@@ -495,42 +293,14 @@ exports[`useDeleteOneRecord B. Starting from filled cache 3. Should handle optim
"city": "ASd",
"company": {
"__typename": "Company",
"address": {
"__typename": "Address",
"addressCity": "Dublin",
"addressCountry": "Ireland",
"addressLat": null,
"addressLng": null,
"addressPostcode": null,
"addressState": null,
"addressStreet1": "Eutaw Street",
"addressStreet2": null,
},
"createdAt": "2025-02-16T08:21:51.715Z",
"createdBy": {
"__typename": "Actor",
"context": {},
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"domainName": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "https://linkedin.com",
"secondaryLinks": [],
},
"employees": null,
"id": "20202020-3ec3-4fe3-8997-b76aa0bfa408",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": "Linkedin",
"position": 1,
},
"createdAt": "2025-01-02T09:52:46.814Z",
"createdBy": {
@@ -633,94 +403,21 @@ exports[`useDeleteOneRecord B. Starting from filled cache 3. Should handle optim
{
"__typename": "Person",
"avatarUrl": null,
"city": "Seattle",
"createdAt": "2025-01-01T09:50:00.000Z",
"createdBy": {
"__typename": "Actor",
"context": null,
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"emails": {
"additionalEmails": [],
"primaryEmail": "christoph.calisto@linkedin.com",
},
"id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5",
"intro": "",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Christoph",
"lastName": "Callisto",
},
"performanceRating": null,
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234562",
},
"position": 1,
"updatedAt": "2025-01-01T09:50:00.000Z",
"whatsapp": {
"additionalPhones": [],
"primaryPhoneCallingCode": "",
"primaryPhoneCountryCode": "",
"primaryPhoneNumber": "",
},
"workPreference": null,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
{
"__typename": "Person",
"city": "Los Angeles",
"createdAt": "2025-01-02T09:48:36.193Z",
"createdBy": {
"__typename": "Actor",
"name": "Tim Apple",
"source": "MANUAL",
"workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7",
},
"deletedAt": null,
"id": "20202020-ac73-4797-824e-87a1f5aea9e0",
"jobTitle": "",
"linkedinLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
"name": {
"__typename": "FullName",
"firstName": "Sylvie",
"lastName": "Palmer",
},
"phones": {
"additionalPhones": [],
"primaryPhoneCallingCode": "+33",
"primaryPhoneCountryCode": "FR",
"primaryPhoneNumber": "781234576",
},
"position": 2,
"xLink": {
"__typename": "Links",
"primaryLinkLabel": "",
"primaryLinkUrl": "",
"secondaryLinks": [],
},
},
],
"position": 1,
@@ -2,7 +2,7 @@ import { renderHook, waitFor } from '@testing-library/react';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import {
personIds,
personRecords,
@@ -18,6 +18,8 @@ import { type MockedResponse } from '@apollo/client/testing';
import { act } from 'react';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
const getDefaultMocks = (
overrides?: Partial<MockedResponse>,
): MockedResponse[] => [
@@ -41,7 +43,7 @@ const mockRefetchAggregateQueries = jest.fn();
refetchAggregateQueries: mockRefetchAggregateQueries,
});
const objectMetadataItem = getMockPersonObjectMetadataItem();
const objectMetadataItems = [objectMetadataItem];
const objectMetadataItems = generatedMockObjectMetadataItems;
const expectedCachedRecordsWithDeletedAt = personRecords.map(
(personRecord) => ({
...personRecord,
@@ -117,9 +119,11 @@ describe('useDeleteManyRecords', () => {
objectMetadataItem,
objectMetadataItems,
record,
recordGqlFields: computeDepthOneRecordGqlFieldsFromRecord({
recordGqlFields: generateDepthRecordGqlFieldsFromRecord({
objectMetadataItems: generatedMockObjectMetadataItems,
objectMetadataItem,
record,
depth: 1,
}),
objectPermissionsByObjectMetadataId: {},
}),
@@ -46,27 +46,7 @@ const mocks: MockedResponse[] = [
node {
__typename
avatarUrl
city
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
@@ -77,58 +57,13 @@ const mocks: MockedResponse[] = [
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
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
@@ -136,208 +71,48 @@ const mocks: MockedResponse[] = [
id
note {
__typename
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
pet {
__typename
age
averageCostOfKibblePerMonth {
amountMicros
currencyCode
}
bio
birthday
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
extraData
id
interestingFacts
isGoodWithKids
location {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
makesOwnerThinkOf {
firstName
lastName
}
name
pictures {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
position
soundSwag
species
traits
updatedAt
vetEmail {
primaryEmail
additionalEmails
}
vetPhone {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
}
petId
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
position
updatedAt
}
rocketId
surveyResult {
__typename
averageEstimatedNumberOfAtomsInTheUniverse
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
participants
percentageOfCompletion
position
score
shortNotes
updatedAt
}
surveyResultId
updatedAt
}
}
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
taskTargets {
edges {
@@ -345,58 +120,13 @@ const mocks: MockedResponse[] = [
__typename
company {
__typename
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
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
@@ -404,217 +134,48 @@ const mocks: MockedResponse[] = [
id
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
pet {
__typename
age
averageCostOfKibblePerMonth {
amountMicros
currencyCode
}
bio
birthday
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
extraData
id
interestingFacts
isGoodWithKids
location {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
makesOwnerThinkOf {
firstName
lastName
}
name
pictures {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
position
soundSwag
species
traits
updatedAt
vetEmail {
primaryEmail
additionalEmails
}
vetPhone {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
}
petId
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
position
updatedAt
}
rocketId
surveyResult {
__typename
averageEstimatedNumberOfAtomsInTheUniverse
comments
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
participants
percentageOfCompletion
position
score
shortNotes
updatedAt
}
surveyResultId
task {
__typename
assigneeId
bodyV2 {
blocknote
markdown
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
}
}
}
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
cursor
}
@@ -1,6 +1,6 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { type RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { generateAggregateQuery } from '@/object-record/utils/generateAggregateQuery';
@@ -11,8 +11,8 @@ import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreate
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
@@ -54,12 +54,16 @@ export const useCreateManyRecords = <
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const objectMetadataHasCreatedByField =
hasObjectMetadataItemFieldCreatedBy(objectMetadataItem);
const computedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const { createManyRecordsMutation } = useCreateManyRecordsMutation({
objectNameSingular,
@@ -11,8 +11,8 @@ import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreate
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
@@ -23,7 +23,6 @@ import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/g
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
type useCreateOneRecordProps = {
objectNameSingular: string;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
@@ -46,8 +45,13 @@ export const useCreateOneRecord = <
objectNameSingular,
});
const computedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const { createOneRecordMutation } = useCreateOneRecordMutation({
objectNameSingular,
@@ -1,15 +1,11 @@
import gql from 'graphql-tag';
import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { generateCreateOneRecordMutation } from '@/object-metadata/utils/generateCreateOneRecordMutation';
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
import { capitalize } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateOneRecordMutation = ({
@@ -23,12 +19,6 @@ export const useCreateOneRecordMutation = ({
objectNameSingular,
});
const appliedRecordGqlFields =
recordGqlFields ??
generateDepthOneRecordGqlFields({
objectMetadataItem,
});
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
@@ -37,22 +27,12 @@ export const useCreateOneRecordMutation = ({
return { createOneRecordMutation: EMPTY_MUTATION };
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const mutationResponseField = getCreateOneRecordMutationResponseField(
objectMetadataItem.nameSingular,
);
const createOneRecordMutation = gql`
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
${mutationResponseField}(data: $input) ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
recordGqlFields: appliedRecordGqlFields,
objectPermissionsByObjectMetadataId,
})}
}
`;
const createOneRecordMutation = generateCreateOneRecordMutation({
objectMetadataItem,
objectMetadataItems,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
return {
createOneRecordMutation,
@@ -5,9 +5,9 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -31,10 +31,15 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
objectNameSingular,
});
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const apolloCoreClient = useApolloCoreClient();
const computedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const { findOneRecordQuery } = useFindOneRecordQuery({
objectNameSingular,
@@ -1,11 +1,10 @@
import { useLazyQuery, type WatchQueryFetchPolicy } from '@apollo/client';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -26,17 +25,17 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
withSoftDeleted = false,
fetchPolicy = 'cache-first',
}: UseLazyFindOneRecordParams) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const apolloCoreClient = useApolloCoreClient();
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const { findOneRecordQuery } = useFindOneRecordQuery({
objectNameSingular,
recordGqlFields:
recordGqlFields ??
generateDepthOneRecordGqlFields({ objectMetadataItem }),
recordGqlFields: recordGqlFields ?? depthOneRecordGqlFields,
withSoftDeleted,
});
@@ -2,8 +2,8 @@ import { useCallback, useState } from 'react';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery';
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
import { useMergeManyRecordsMutation } from '@/object-record/hooks/useMergeManyRecordsMutation';
@@ -34,8 +34,13 @@ export const useMergeManyRecords = <
objectNameSingular,
});
const computedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const { mergeManyRecordsMutation } = useMergeManyRecordsMutation({
objectNameSingular,
@@ -5,8 +5,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getMergeManyRecordsMutationResponseField } from '@/object-record/utils/getMergeManyRecordsMutationResponseField';
import { capitalize } from 'twenty-shared/utils';
@@ -22,13 +22,14 @@ export const useMergeManyRecordsMutation = ({
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const appliedRecordGqlFields =
recordGqlFields ??
generateDepthOneRecordGqlFields({
objectMetadataItem,
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const appliedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
@@ -3,7 +3,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpdateOneRecordV2 } from '@/object-record/hooks/useUpdateOneRecordV2';
import { CustomError, isDefined } from 'twenty-shared/utils';
@@ -80,9 +80,11 @@ export const useRecordOneToManyFieldAttachTargetRecord = () => {
[targetGQLFieldName]: previousRecord,
};
const gqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const gqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem: targetObjectMetadataItem,
objectMetadataItems,
record: previousRecordWithRelation,
depth: 1,
});
updateRecordFromCache({
@@ -7,8 +7,8 @@ import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordF
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
@@ -42,8 +42,13 @@ export const useUpdateOneRecord = <
objectNameSingular,
});
const computedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular,
@@ -109,9 +114,11 @@ export const useUpdateOneRecord = <
isDefined(cachedRecordWithConnection);
if (shouldHandleOptimisticCache) {
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: optimisticRecordInput,
depth: 1,
});
updateRecordFromCache({
@@ -171,9 +178,11 @@ export const useUpdateOneRecord = <
).filter((diffKey) => !cachedRecordKeys.has(diffKey));
const recordGqlFields = {
...computeDepthOneRecordGqlFieldsFromRecord({
...generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: cachedRecord,
depth: 1,
}),
...buildRecordFromKeysWithSameValue(
recordKeysAddedByOptimisticCache,
@@ -7,9 +7,9 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
@@ -57,7 +57,11 @@ export const useUpdateOneRecordV2 = () => {
const computedRecordGqlFields =
recordGqlFields ??
generateDepthOneRecordGqlFields({ objectMetadataItem });
generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth: 1,
});
const optimisticRecordInput =
optimisticRecord ??
@@ -107,9 +111,11 @@ export const useUpdateOneRecordV2 = () => {
isDefined(cachedRecordWithConnection);
if (shouldHandleOptimisticCache) {
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: optimisticRecordInput,
depth: 1,
});
updateRecordFromCache({
@@ -178,9 +184,11 @@ export const useUpdateOneRecordV2 = () => {
).filter((diffKey) => !cachedRecordKeys.has(diffKey));
const recordGqlFields = {
...computeDepthOneRecordGqlFieldsFromRecord({
...generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: cachedRecord,
depth: 1,
}),
...buildRecordFromKeysWithSameValue(
recordKeysAddedByOptimisticCache,
@@ -3,7 +3,7 @@ import { renderHook, waitFor } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { type RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
@@ -6,11 +6,11 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { type RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
import isEmpty from 'lodash.isempty';
import { capitalize } from 'twenty-shared/utils';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
export const useGenerateCombinedFindManyRecordsQuery = ({
operationSignatures,
@@ -98,7 +98,9 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
objectMetadataItem,
recordGqlFields:
operationSignature.fields ??
generateDepthOneRecordGqlFields({
generateDepthRecordGqlFieldsFromObject({
objectMetadataItems,
depth: 1,
objectMetadataItem,
}),
objectPermissionsByObjectMetadataId,
@@ -2,23 +2,22 @@ import { type ApolloClient, gql } from '@apollo/client';
import { isUndefined } from '@sniptt/guards';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { type CombinedFindManyRecordsQueryResult } from '@/object-record/multiple-objects/types/CombinedFindManyRecordsQueryResult';
import { generateCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/utils/generateCombinedFindManyRecordsQueryVariables';
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
import { useRecoilValue } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
export const usePerformCombinedFindManyRecords = () => {
const apolloCoreClient = useApolloCoreClient();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
@@ -86,8 +85,10 @@ export const usePerformCombinedFindManyRecords = () => {
objectMetadataItem,
recordGqlFields:
operationSignature.fields ??
generateDepthOneRecordGqlFields({
generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth: 1,
}),
objectPermissionsByObjectMetadataId,
})}
@@ -1,4 +1,4 @@
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { type RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { generateCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/utils/generateCombinedFindManyRecordsQueryVariables';
@@ -3,14 +3,16 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext';
import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
import { recordCalendarSelectedRecordIdsComponentSelector } from '@/object-record/record-calendar/states/selectors/recordCalendarSelectedRecordIdsComponentSelector';
import { useRecordFieldGqlFields } from '@/object-record/record-field/hooks/useRecordFieldGqlFields';
import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { recordIndexCalendarFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
export const RecordIndexCalendarDataLoaderEffect = () => {
const recordCalendarId = useAvailableComponentInstanceIdOrThrow(
@@ -34,11 +36,18 @@ export const RecordIndexCalendarDataLoaderEffect = () => {
contextStoreTargetedRecordsRuleComponentState,
);
const recordIndexCalendarFieldMetadataId = useRecoilValue(
recordIndexCalendarFieldMetadataIdState,
);
const objectNameSingular = objectMetadataItem.nameSingular;
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const recordGqlFields = useRecordFieldGqlFields({ objectMetadataItem });
const recordGqlFields = useRecordsFieldVisibleGqlFields({
objectMetadataItem,
additionalFieldMetadataId: recordIndexCalendarFieldMetadataId,
});
const { records } = useFindManyRecords({
...params,
@@ -1,102 +0,0 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
export const useRecordFieldGqlFields = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
);
const { fieldMetadataItemByFieldMetadataItemId } =
useRecordIndexContextOrThrow();
const { objectMetadataItem: noteTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.NoteTarget,
});
const { objectMetadataItem: taskTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.TaskTarget,
});
const allDepthOneWithoutRelationsRecordGqlFields =
generateDepthOneWithoutRelationsRecordGqlFields({
objectMetadataItem,
});
const gqlFieldsList: RecordGqlFields = Object.fromEntries(
visibleRecordFields.flatMap(
(recordField): [string, boolean | RecordGqlFields][] => {
const fieldMetadataItem: FieldMetadataItem | undefined =
fieldMetadataItemByFieldMetadataItemId[
recordField.fieldMetadataItemId
];
if (!isDefined(fieldMetadataItem)) {
throw new Error(
`Field ${recordField.fieldMetadataItemId} is missing, please refresh the page. If the problem persists, please contact support.`,
);
}
// TODO: remove this once we have made the workflowVersion lighter
if (
fieldMetadataItem.name === 'versions' ||
fieldMetadataItem.name === 'workflowVersion'
) {
return [[fieldMetadataItem.name, { id: true, name: true }]];
}
const isMorphRelation =
fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION;
if (!isMorphRelation) {
return [[fieldMetadataItem.name, true]];
}
if (!isDefined(fieldMetadataItem.morphRelations)) {
throw new Error(
`Field ${fieldMetadataItem.name} is missing, please refresh the page. If the problem persists, please contact support.`,
);
}
return fieldMetadataItem.morphRelations.map((morphRelation) => {
const computedFieldName = computeMorphRelationFieldName({
fieldName: fieldMetadataItem.name,
relationType: fieldMetadataItem.settings?.relationType,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
});
return [computedFieldName, true];
});
},
),
);
return {
...allDepthOneWithoutRelationsRecordGqlFields,
...gqlFieldsList,
noteTargets: generateDepthOneRecordGqlFields({
objectMetadataItem: noteTargetObjectMetadataItem,
}),
taskTargets: generateDepthOneRecordGqlFields({
objectMetadataItem: taskTargetObjectMetadataItem,
}),
};
};
@@ -0,0 +1,86 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getImageIdentifierFieldMetadataItem } from '@/object-metadata/utils/getImageIdentifierFieldMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField';
import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
type UseRecordsFieldVisibleGqlFields = {
objectMetadataItem: ObjectMetadataItem;
additionalFieldMetadataId?: string | null;
};
export const useRecordsFieldVisibleGqlFields = ({
objectMetadataItem,
additionalFieldMetadataId,
}: UseRecordsFieldVisibleGqlFields) => {
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
);
const { fieldMetadataItemByFieldMetadataItemId } =
useRecordIndexContextOrThrow();
const { objectMetadataItems } = useObjectMetadataItems();
const { objectMetadataItem: noteTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.NoteTarget,
});
const { objectMetadataItem: taskTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.TaskTarget,
});
const allDepthOneGqlFields = generateDepthRecordGqlFieldsFromFields({
objectMetadataItems,
fields: visibleRecordFields.map(
(field) =>
fieldMetadataItemByFieldMetadataItemId[field.fieldMetadataItemId],
),
depth: 1,
});
const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const imageIdentifierFieldMetadataItem =
getImageIdentifierFieldMetadataItem(objectMetadataItem);
const hasPosition = hasObjectMetadataItemPositionField(objectMetadataItem);
const additionalFieldMetadataItem = isDefined(additionalFieldMetadataId)
? fieldMetadataItemByFieldMetadataItemId[additionalFieldMetadataId]
: undefined;
return {
id: true,
...(isDefined(additionalFieldMetadataItem)
? { [additionalFieldMetadataItem.name]: true }
: {}),
...(isDefined(labelIdentifierFieldMetadataItem)
? { [labelIdentifierFieldMetadataItem.name]: true }
: {}),
...(isDefined(imageIdentifierFieldMetadataItem)
? { [imageIdentifierFieldMetadataItem.name]: true }
: {}),
...(hasPosition ? { position: true } : {}),
...allDepthOneGqlFields,
noteTargets: generateDepthRecordGqlFieldsFromObject({
objectMetadataItem: noteTargetObjectMetadataItem,
objectMetadataItems,
depth: 1,
}),
taskTargets: generateDepthRecordGqlFieldsFromObject({
objectMetadataItems,
objectMetadataItem: taskTargetObjectMetadataItem,
depth: 1,
}),
};
};
@@ -1,9 +1,9 @@
import { gql } from '@apollo/client';
import { type MockedResponse } from '@apollo/client/testing';
import { renderHook, waitFor } from '@testing-library/react';
import { act, type ReactNode } from 'react';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { booleanFieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions';
import {
@@ -14,290 +14,23 @@ import {
import { useToggleEditOnlyInput } from '@/object-record/record-field/ui/hooks/useToggleEditOnlyInput';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const recordId = 'recordId';
const companyObjectMetadataItem = getMockObjectMetadataItemOrThrow('company');
const updateOneCompanyMutation = generateUpdateOneRecordMutation({
objectMetadataItem: companyObjectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
computeReferences: false,
objectPermissionsByObjectMetadataId: {},
});
const mocks: MockedResponse[] = [
{
request: {
query: gql`
mutation UpdateOneCompany(
$idToUpdate: UUID!
$input: CompanyUpdateInput!
) {
updateCompany(id: $idToUpdate, data: $input) {
__typename
accountOwner {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
accountOwnerId
address {
addressStreet1
addressStreet2
addressCity
addressState
addressCountry
addressPostcode
addressLat
addressLng
}
annualRecurringRevenue {
amountMicros
currencyCode
}
attachments {
edges {
node {
__typename
authorId
companyId
createdAt
deletedAt
fullPath
id
name
noteId
opportunityId
personId
petId
rocketId
surveyResultId
taskId
type
updatedAt
}
}
}
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
domainName {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
employees
favorites {
edges {
node {
__typename
companyId
createdAt
deletedAt
favoriteFolderId
forWorkspaceMemberId
id
noteId
opportunityId
personId
petId
position
rocketId
surveyResultId
taskId
updatedAt
viewId
workflowId
workflowRunId
workflowVersionId
}
}
}
id
idealCustomerProfile
introVideo {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name
noteTargets {
edges {
node {
__typename
companyId
createdAt
deletedAt
id
noteId
opportunityId
personId
petId
rocketId
surveyResultId
updatedAt
}
}
}
opportunities {
edges {
node {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
}
}
people {
edges {
node {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
}
}
position
tagline
taskTargets {
edges {
node {
__typename
companyId
createdAt
deletedAt
id
opportunityId
personId
petId
rocketId
surveyResultId
taskId
updatedAt
}
}
}
timelineActivities {
edges {
node {
__typename
companyId
createdAt
deletedAt
happensAt
id
linkedObjectMetadataId
linkedRecordCachedName
linkedRecordId
name
noteId
opportunityId
personId
petId
properties
rocketId
surveyResultId
taskId
updatedAt
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}
}
updatedAt
visaSponsorship
workPolicy
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
}
`,
query: updateOneCompanyMutation,
variables: {
idToUpdate: 'recordId',
input: { idealCustomerProfile: true },
@@ -9,12 +9,13 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields';
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import {
@@ -79,10 +80,14 @@ export const useLoadRecordIndexBoardColumn = ({
});
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
const recordGqlFields = useRecordBoardRecordGqlFields({
objectMetadataItem,
const recordGroupFieldMetadata = useRecoilComponentValue(
recordGroupFieldMetadataComponentState,
recordBoardId,
);
const recordGqlFields = useRecordsFieldVisibleGqlFields({
objectMetadataItem,
additionalFieldMetadataId: recordGroupFieldMetadata?.id,
});
const recordIndexKanbanFieldMetadataFilterValue = isDefined(
@@ -1,77 +0,0 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
export const useRecordBoardRecordGqlFields = ({
objectMetadataItem,
recordBoardId,
}: {
recordBoardId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
recordBoardId,
);
const { fieldMetadataItemByFieldMetadataItemId } =
useRecordIndexContextOrThrow();
const recordGroupFieldMetadata = useRecoilComponentValue(
recordGroupFieldMetadataComponentState,
recordBoardId,
);
const { objectMetadataItem: noteTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.NoteTarget,
});
const { objectMetadataItem: taskTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.TaskTarget,
});
const allDepthOneWithoutRelationsRecordGqlFields =
generateDepthOneWithoutRelationsRecordGqlFields({
objectMetadataItem,
});
const visibleFieldMetadataItems = visibleRecordFields
.map(
(recordField) =>
fieldMetadataItemByFieldMetadataItemId[
recordField.fieldMetadataItemId
] ?? null,
)
.filter(isDefined);
const recordGqlFields: Record<string, any> = {
...allDepthOneWithoutRelationsRecordGqlFields,
...Object.fromEntries(
visibleFieldMetadataItems.map((visibleFieldMetadataItem) => [
visibleFieldMetadataItem.name,
true,
]),
),
noteTargets: generateDepthOneRecordGqlFields({
objectMetadataItem: noteTargetObjectMetadataItem,
}),
taskTargets: generateDepthOneRecordGqlFields({
objectMetadataItem: taskTargetObjectMetadataItem,
}),
};
if (isDefined(recordGroupFieldMetadata?.name)) {
recordGqlFields[recordGroupFieldMetadata.name] = true;
}
return recordGqlFields;
};
@@ -1,16 +1,17 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
import { useRecordFieldGqlFields } from '@/object-record/record-field/hooks/useRecordFieldGqlFields';
import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
export const useRecordIndexTableFetchMore = (objectNameSingular: string) => {
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const recordGqlFields = useRecordFieldGqlFields({ objectMetadataItem });
const recordGqlFields = useRecordsFieldVisibleGqlFields({
objectMetadataItem,
});
const { fetchMoreRecordsLazy, queryIdentifier } = useLazyFindManyRecords({
...params,
@@ -1,20 +1,21 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useRecordFieldGqlFields } from '@/object-record/record-field/hooks/useRecordFieldGqlFields';
import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
export const useRecordIndexTableQuery = (objectNameSingular: string) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const showAuthModal = useShowAuthModal();
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const recordGqlFields = useRecordFieldGqlFields({ objectMetadataItem });
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const recordGqlFields = useRecordsFieldVisibleGqlFields({
objectMetadataItem,
});
const { records, hasNextPage, queryIdentifier, loading } = useFindManyRecords(
{
@@ -1,46 +1,56 @@
import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperationSignatureFactory =
({
objectMetadataItem,
objectMetadataItems,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
}) => ({
objectNameSingular: objectMetadataItem.nameSingular,
variables: {},
fields: {
...generateDepthOneRecordGqlFields({ objectMetadataItem }),
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Task
? {
taskTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
note: true,
noteId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {}),
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Note
? {
noteTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
task: true,
taskId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {}),
},
});
type FindOneRecordForShowPageOperationSignatureFactory = {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
};
export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperationSignatureFactory<
FindOneRecordForShowPageOperationSignatureFactory
> = ({
objectMetadataItem,
objectMetadataItems,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
}) => ({
objectNameSingular: objectMetadataItem.nameSingular,
variables: {},
fields: {
...generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth: 1,
}),
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Task
? {
taskTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
note: true,
noteId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {}),
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Note
? {
noteTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
task: true,
taskId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {}),
},
});
@@ -1,6 +1,6 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useBatchCreateManyRecords } from '@/object-record/hooks/useBatchCreateManyRecords';
import { useBuildSpreadsheetImportFields } from '@/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields';
import { buildRecordFromImportedStructuredRow } from '@/object-record/spreadsheet-import/utils/buildRecordFromImportedStructuredRow';
@@ -32,11 +32,14 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
const abortController = new AbortController();
const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 0,
});
const { batchCreateManyRecords } = useBatchCreateManyRecords({
objectNameSingular,
recordGqlFields: generateDepthOneWithoutRelationsRecordGqlFields({
objectMetadataItem,
}),
recordGqlFields,
mutationBatchSize: SPREADSHEET_IMPORT_CREATE_RECORDS_BATCH_SIZE,
setBatchedRecordsCount: setCreatedRecordsProgress,
abortController,
@@ -1,6 +1,6 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { type FieldActorForInputValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { InMemoryCache } from '@apollo/client';
@@ -128,9 +128,11 @@ describe('computeOptimisticRecordFromInput', () => {
(field) => field.name === 'id',
),
};
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
record: companyRecord,
depth: 1,
});
updateRecordFromCache({
objectMetadataItems: generatedMockObjectMetadataItems,
@@ -178,9 +180,11 @@ describe('computeOptimisticRecordFromInput', () => {
}),
],
};
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
depth: 1,
objectMetadataItem,
record: companyRecord,
objectMetadataItems: generatedMockObjectMetadataItems,
});
updateRecordFromCache({
objectMetadataItems: generatedMockObjectMetadataItems,
@@ -1,7 +1,7 @@
import gql from 'graphql-tag';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName';
import { capitalize } from 'twenty-shared/utils';
@@ -5,6 +5,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { type Favorite } from '@/favorites/types/Favorite';
import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
@@ -36,19 +37,19 @@ export const PrefetchRunFavoriteQueriesEffect = () => {
prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders),
);
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Favorite,
});
const findAllFavoritesOperationSignature =
findAllFavoritesOperationSignatureFactory({
objectMetadataItem: objectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.Favorite,
),
objectMetadataItem: favoriteObjectMetadataItem,
objectMetadataItems,
});
const findAllFavoriteFoldersOperationSignature =
findAllFavoritesFolderOperationSignatureFactory({
objectMetadataItem: objectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.FavoriteFolder,
),
});
findAllFavoritesFolderOperationSignatureFactory({});
const { records: favorites } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Favorite,
@@ -1,22 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { findAllFavoritesFolderOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesFolderOperationSignatureFactory';
import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesOperationSignatureFactory';
import { type PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const PREFETCH_CONFIG: Record<
PrefetchKey,
{
objectNameSingular: CoreObjectNameSingular;
operationSignatureFactory: RecordGqlOperationSignatureFactory;
}
> = {
ALL_FAVORITES: {
objectNameSingular: CoreObjectNameSingular.Favorite,
operationSignatureFactory: findAllFavoritesOperationSignatureFactory,
},
ALL_FAVORITES_FOLDERS: {
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
operationSignatureFactory: findAllFavoritesFolderOperationSignatureFactory,
},
};
@@ -1,7 +1,7 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const findAllFavoritesFolderOperationSignatureFactory: RecordGqlOperationSignatureFactory =
export const findAllFavoritesFolderOperationSignatureFactory: RecordGqlOperationSignatureFactory<{}> =
() => ({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
variables: {},
@@ -1,15 +1,26 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
export const findAllFavoritesOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({ objectMetadataItem }: { objectMetadataItem: ObjectMetadataItem }) => ({
objectNameSingular: CoreObjectNameSingular.Favorite,
variables: {},
fields: {
...generateDepthOneRecordGqlFields({
objectMetadataItem,
}),
},
});
type FindAllFavoritesOperationSignatureFactory = {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
};
export const findAllFavoritesOperationSignatureFactory: RecordGqlOperationSignatureFactory<
FindAllFavoritesOperationSignatureFactory
> = ({
objectMetadataItem,
objectMetadataItems,
}: FindAllFavoritesOperationSignatureFactory) => ({
objectNameSingular: CoreObjectNameSingular.Favorite,
variables: {},
fields: {
...generateDepthRecordGqlFieldsFromObject({
objectMetadataItems,
objectMetadataItem,
depth: 1,
}),
},
});
@@ -1,30 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const findAllViewsOperationSignatureFactory: RecordGqlOperationSignatureFactory =
() => ({
objectNameSingular: CoreObjectNameSingular.View,
variables: {},
fields: {
id: true,
createdAt: true,
updatedAt: true,
isCompact: true,
openRecordIn: true,
objectMetadataId: true,
position: true,
type: true,
kanbanFieldMetadataId: true,
kanbanAggregateOperation: true,
kanbanAggregateOperationFieldMetadataId: true,
name: true,
icon: true,
key: true,
viewFilters: true,
viewFilterGroups: true,
viewSorts: true,
viewFields: true,
viewGroups: true,
anyFieldFilterValue: true,
},
});
@@ -4,9 +4,8 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails';
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
@@ -32,10 +31,6 @@ export const SettingsAccountsMessageChannelsContainer = () => {
settingsAccountsSelectedMessageChannelState,
);
const messageChannelObjectMetadataItem = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.MessageChannel,
});
const { records: accounts } = useFindManyRecords<ConnectedAccount>({
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
filter: {
@@ -45,6 +40,11 @@ export const SettingsAccountsMessageChannelsContainer = () => {
},
});
const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular: CoreObjectNameSingular.MessageChannel,
depth: 1,
});
const { records: messageChannels } = useFindManyRecords<
MessageChannel & {
connectedAccount: ConnectedAccount;
@@ -59,9 +59,7 @@ export const SettingsAccountsMessageChannelsContainer = () => {
eq: true,
},
},
recordGqlFields: generateDepthOneRecordGqlFields(
messageChannelObjectMetadataItem,
),
recordGqlFields,
onCompleted: (data) => {
setSelectedMessageChannel(data[0]);
},
@@ -6,7 +6,7 @@ import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordF
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -37,8 +37,9 @@ export const ListenRecordUpdatesEffect = ({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const computedRecordGqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem,
const computedRecordGqlFields = useGenerateDepthRecordGqlFieldsFromObject({
depth: 1,
objectNameSingular,
});
const setRecordInStore = useRecoilCallback(
@@ -9,7 +9,7 @@ import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreate
import { useUpsertFindOneRecordQueryInCache } from '@/object-record/cache/hooks/useUpsertFindOneRecordQueryInCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
@@ -31,14 +31,14 @@ export const useRunWorkflowVersion = () => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const createOneRecordInCache = useCreateOneRecordInCache<WorkflowRun>({
objectMetadataItem,
});
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItems } = useObjectMetadataItems();
const [mutate] = useMutation<
RunWorkflowVersionMutation,
RunWorkflowVersionMutationVariables
@@ -46,8 +46,9 @@ export const useRunWorkflowVersion = () => {
client: apolloCoreClient,
});
const computedRecordGqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem,
const computedRecordGqlFields = useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
depth: 1,
});
const { upsertFindOneRecordQueryInCache } =
@@ -1,8 +1,7 @@
import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsAccountLoader } from '@/settings/accounts/components/SettingsAccountLoader';
import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsBlocklistSection';
@@ -21,8 +20,10 @@ export const SettingsAccounts = () => {
const { t } = useLingui();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItem } = useObjectMetadataItem({
const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
depth: 1,
shouldOnlyLoadRelationIdentifiers: false,
});
const { records: accounts, loading } = useFindManyRecords<ConnectedAccount>({
@@ -32,7 +33,7 @@ export const SettingsAccounts = () => {
eq: currentWorkspaceMember?.id,
},
},
recordGqlFields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
recordGqlFields,
});
return (
@@ -1,7 +1,7 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { InMemoryCache, type NormalizedCacheObject } from '@apollo/client';
@@ -57,7 +57,9 @@ export class InMemoryTestingCacheInstance {
objectMetadataItem,
objectMetadataItems: this.objectMetadataItems,
record,
recordGqlFields: computeDepthOneRecordGqlFieldsFromRecord({
recordGqlFields: generateDepthRecordGqlFieldsFromRecord({
objectMetadataItems: this.objectMetadataItems,
depth: 1,
objectMetadataItem,
record,
}),
@@ -1,5 +1,5 @@
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { prefillRecord } from '@/object-record/utils/prefillRecord';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
@@ -33,8 +33,10 @@ export const generateEmptyJestRecordNode = ({
objectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
recordGqlFields: withDepthOneRelation
? generateDepthOneRecordGqlFields({
? generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
depth: 1,
})
: undefined,
});