diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 111e7a1751..11ff685dec 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -259,11 +259,19 @@ export type ObjectRecordFilterInput = { export type Query = { __typename?: 'Query'; + /** @deprecated Use getTimelineCalendarEventsFromObjectRecord instead */ getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal; + getTimelineCalendarEventsFromObjectRecord: TimelineCalendarEventsWithTotal; + /** @deprecated Use getTimelineCalendarEventsFromObjectRecord instead */ getTimelineCalendarEventsFromOpportunityId: TimelineCalendarEventsWithTotal; + /** @deprecated Use getTimelineCalendarEventsFromObjectRecord instead */ getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal; + /** @deprecated Use getTimelineThreadsFromObjectRecord instead */ getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal; + getTimelineThreadsFromObjectRecord: TimelineThreadsWithTotal; + /** @deprecated Use getTimelineThreadsFromObjectRecord instead */ getTimelineThreadsFromOpportunityId: TimelineThreadsWithTotal; + /** @deprecated Use getTimelineThreadsFromObjectRecord instead */ getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; isMaintenanceModeBannerDismissed: Scalars['Boolean']['output']; search: SearchResultConnection; @@ -278,6 +286,14 @@ export type QueryGetTimelineCalendarEventsFromCompanyIdArgs = { }; +export type QueryGetTimelineCalendarEventsFromObjectRecordArgs = { + objectNameSingular: Scalars['String']['input']; + page: Scalars['Int']['input']; + pageSize: Scalars['Int']['input']; + recordId: Scalars['UUID']['input']; +}; + + export type QueryGetTimelineCalendarEventsFromOpportunityIdArgs = { opportunityId: Scalars['UUID']['input']; page: Scalars['Int']['input']; @@ -299,6 +315,14 @@ export type QueryGetTimelineThreadsFromCompanyIdArgs = { }; +export type QueryGetTimelineThreadsFromObjectRecordArgs = { + objectNameSingular: Scalars['String']['input']; + page: Scalars['Int']['input']; + pageSize: Scalars['Int']['input']; + recordId: Scalars['UUID']['input']; +}; + + export type QueryGetTimelineThreadsFromOpportunityIdArgs = { opportunityId: Scalars['UUID']['input']; page: Scalars['Int']['input']; @@ -594,32 +618,15 @@ export type TimelineCalendarEventParticipantFragmentFragment = { __typename?: 'T export type TimelineCalendarEventsWithTotalFragmentFragment = { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> }; -export type GetTimelineCalendarEventsFromCompanyIdQueryVariables = Exact<{ - companyId: Scalars['UUID']['input']; +export type GetTimelineCalendarEventsFromObjectRecordQueryVariables = Exact<{ + objectNameSingular: Scalars['String']['input']; + recordId: Scalars['UUID']['input']; page: Scalars['Int']['input']; pageSize: Scalars['Int']['input']; }>; -export type GetTimelineCalendarEventsFromCompanyIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromCompanyId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; - -export type GetTimelineCalendarEventsFromOpportunityIdQueryVariables = Exact<{ - opportunityId: Scalars['UUID']['input']; - page: Scalars['Int']['input']; - pageSize: Scalars['Int']['input']; -}>; - - -export type GetTimelineCalendarEventsFromOpportunityIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromOpportunityId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; - -export type GetTimelineCalendarEventsFromPersonIdQueryVariables = Exact<{ - personId: Scalars['UUID']['input']; - page: Scalars['Int']['input']; - pageSize: Scalars['Int']['input']; -}>; - - -export type GetTimelineCalendarEventsFromPersonIdQuery = { __typename?: 'Query', getTimelineCalendarEventsFromPersonId: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; +export type GetTimelineCalendarEventsFromObjectRecordQuery = { __typename?: 'Query', getTimelineCalendarEventsFromObjectRecord: { __typename?: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: number, timelineCalendarEvents: Array<{ __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }; @@ -627,32 +634,15 @@ export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id export type TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> }; -export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{ - companyId: Scalars['UUID']['input']; +export type GetTimelineThreadsFromObjectRecordQueryVariables = Exact<{ + objectNameSingular: Scalars['String']['input']; + recordId: Scalars['UUID']['input']; page: Scalars['Int']['input']; pageSize: Scalars['Int']['input']; }>; -export type GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; - -export type GetTimelineThreadsFromOpportunityIdQueryVariables = Exact<{ - opportunityId: Scalars['UUID']['input']; - page: Scalars['Int']['input']; - pageSize: Scalars['Int']['input']; -}>; - - -export type GetTimelineThreadsFromOpportunityIdQuery = { __typename?: 'Query', getTimelineThreadsFromOpportunityId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; - -export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{ - personId: Scalars['UUID']['input']; - page: Scalars['Int']['input']; - pageSize: Scalars['Int']['input']; -}>; - - -export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; +export type GetTimelineThreadsFromObjectRecordQuery = { __typename?: 'Query', getTimelineThreadsFromObjectRecord: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type SearchQueryVariables = Exact<{ searchInput: Scalars['String']['input']; @@ -808,12 +798,8 @@ export const ParticipantFragmentFragmentDoc = {"kind":"Document","definitions":[ export const TimelineThreadFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}}]} as unknown as DocumentNode; export const TimelineThreadsWithTotalFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}}]} as unknown as DocumentNode; export const WorkflowDiffFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkflowDiffFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowVersionStepChanges"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"triggerDiff"}},{"kind":"Field","name":{"kind":"Name","value":"stepsDiff"}}]}}]} as unknown as DocumentNode; -export const GetTimelineCalendarEventsFromCompanyIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineCalendarEventsFromCompanyId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"companyId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineCalendarEventsFromCompanyId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"companyId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"companyId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}}]} as unknown as DocumentNode; -export const GetTimelineCalendarEventsFromOpportunityIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineCalendarEventsFromOpportunityId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"opportunityId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineCalendarEventsFromOpportunityId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"opportunityId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"opportunityId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}}]} as unknown as DocumentNode; -export const GetTimelineCalendarEventsFromPersonIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineCalendarEventsFromPersonId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineCalendarEventsFromPersonId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}}]} as unknown as DocumentNode; -export const GetTimelineThreadsFromCompanyIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromCompanyId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"companyId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromCompanyId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"companyId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"companyId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode; -export const GetTimelineThreadsFromOpportunityIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromOpportunityId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"opportunityId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromOpportunityId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"opportunityId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"opportunityId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode; -export const GetTimelineThreadsFromPersonIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromPersonId"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromPersonId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode; +export const GetTimelineCalendarEventsFromObjectRecordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineCalendarEventsFromObjectRecord"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineCalendarEventsFromObjectRecord"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectNameSingular"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}}},{"kind":"Argument","name":{"kind":"Name","value":"recordId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}}]} as unknown as DocumentNode; +export const GetTimelineThreadsFromObjectRecordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromObjectRecord"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromObjectRecord"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectNameSingular"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}}},{"kind":"Argument","name":{"kind":"Name","value":"recordId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode; export const SearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Search"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"excludedObjectNameSingulars"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"includedObjectNameSingulars"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectRecordFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"search"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchInput"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"excludedObjectNameSingulars"},"value":{"kind":"Variable","name":{"kind":"Name","value":"excludedObjectNameSingulars"}}},{"kind":"Argument","name":{"kind":"Name","value":"includedObjectNameSingulars"},"value":{"kind":"Variable","name":{"kind":"Name","value":"includedObjectNameSingulars"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recordId"}},{"kind":"Field","name":{"kind":"Name","value":"objectNameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"objectLabelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"tsRankCD"}},{"kind":"Field","name":{"kind":"Name","value":"tsRank"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode; export const ActivateWorkflowVersionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActivateWorkflowVersion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workflowVersionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activateWorkflowVersion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workflowVersionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workflowVersionId"}}}]}]}}]} as unknown as DocumentNode; export const ComputeStepOutputSchemaDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ComputeStepOutputSchema"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ComputeStepOutputSchemaInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"computeStepOutputSchema"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventsCard.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventsCard.tsx index c40a4b8e73..6b8ee007cf 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventsCard.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventsCard.tsx @@ -6,14 +6,11 @@ import { format, getYear } from 'date-fns'; import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from '@/activities/calendar/constants/Calendar'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; -import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId'; -import { getTimelineCalendarEventsFromOpportunityId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromOpportunityId'; -import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromPersonId'; +import { getTimelineCalendarEventsFromObjectRecord } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromObjectRecord'; import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents'; import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { H3Title } from 'twenty-ui/typography'; import { @@ -52,33 +49,17 @@ export const CalendarEventsCard = () => { const targetRecord = useTargetRecord(); const { localeCatalog } = useAtomStateValue(dateLocaleState); - const [query, queryName] = - targetRecord.targetObjectNameSingular === CoreObjectNameSingular.Person - ? [ - getTimelineCalendarEventsFromPersonId, - 'getTimelineCalendarEventsFromPersonId', - ] - : targetRecord.targetObjectNameSingular === CoreObjectNameSingular.Company - ? [ - getTimelineCalendarEventsFromCompanyId, - 'getTimelineCalendarEventsFromCompanyId', - ] - : [ - getTimelineCalendarEventsFromOpportunityId, - 'getTimelineCalendarEventsFromOpportunityId', - ]; - const { data, firstQueryLoading, isFetchingMore, fetchMoreRecords } = useCustomResolver( - query, - queryName, + getTimelineCalendarEventsFromObjectRecord, + 'getTimelineCalendarEventsFromObjectRecord', 'timelineCalendarEvents', targetRecord, TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, ); const { timelineCalendarEvents, totalNumberOfCalendarEvents } = - data?.[queryName] ?? {}; + data?.getTimelineCalendarEventsFromObjectRecord ?? {}; const { calendarEventsByDayTime, diff --git a/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx b/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx index 2128194bf9..1ea46989f5 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx @@ -3,7 +3,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { HttpResponse, graphql } from 'msw'; import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard'; -import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId'; +import { getTimelineCalendarEventsFromObjectRecord } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromObjectRecord'; import { CoreObjectNameSingular } from 'twenty-shared/types'; import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { ComponentDecorator } from 'twenty-ui/testing'; @@ -41,12 +41,12 @@ const meta: Meta = { handlers: [ ...graphqlMocks.handlers, graphql.query( - getOperationName(getTimelineCalendarEventsFromCompanyId) ?? '', + getOperationName(getTimelineCalendarEventsFromObjectRecord) ?? '', ({ variables }) => { if (variables.page > 1) { return HttpResponse.json({ data: { - getTimelineCalendarEventsFromCompanyId: { + getTimelineCalendarEventsFromObjectRecord: { __typename: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: 3, timelineCalendarEvents: [], @@ -56,7 +56,7 @@ const meta: Meta = { } return HttpResponse.json({ data: { - getTimelineCalendarEventsFromCompanyId: { + getTimelineCalendarEventsFromObjectRecord: { __typename: 'TimelineCalendarEventsWithTotal', totalNumberOfCalendarEvents: 3, timelineCalendarEvents: mockedTimelineCalendarEvents, diff --git a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId.ts b/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromObjectRecord.ts similarity index 57% rename from packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId.ts rename to packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromObjectRecord.ts index c43d197e43..9e912f756e 100644 --- a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId.ts +++ b/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromObjectRecord.ts @@ -1,14 +1,16 @@ import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventWithTotalFragment'; import { gql } from '@apollo/client'; -export const getTimelineCalendarEventsFromCompanyId = gql` - query GetTimelineCalendarEventsFromCompanyId( - $companyId: UUID! +export const getTimelineCalendarEventsFromObjectRecord = gql` + query GetTimelineCalendarEventsFromObjectRecord( + $objectNameSingular: String! + $recordId: UUID! $page: Int! $pageSize: Int! ) { - getTimelineCalendarEventsFromCompanyId( - companyId: $companyId + getTimelineCalendarEventsFromObjectRecord( + objectNameSingular: $objectNameSingular + recordId: $recordId page: $page pageSize: $pageSize ) { diff --git a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromOpportunityId.ts b/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromOpportunityId.ts deleted file mode 100644 index 84b0f69852..0000000000 --- a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromOpportunityId.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { gql } from '@apollo/client'; - -import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventWithTotalFragment'; - -export const getTimelineCalendarEventsFromOpportunityId = gql` - query GetTimelineCalendarEventsFromOpportunityId( - $opportunityId: UUID! - $page: Int! - $pageSize: Int! - ) { - getTimelineCalendarEventsFromOpportunityId( - opportunityId: $opportunityId - page: $page - pageSize: $pageSize - ) { - ...TimelineCalendarEventsWithTotalFragment - } - } - ${timelineCalendarEventWithTotalFragment} -`; diff --git a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromPersonId.ts b/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromPersonId.ts deleted file mode 100644 index 3285fb475d..0000000000 --- a/packages/twenty-front/src/modules/activities/calendar/graphql/queries/getTimelineCalendarEventsFromPersonId.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventWithTotalFragment'; -import { gql } from '@apollo/client'; - -export const getTimelineCalendarEventsFromPersonId = gql` - query GetTimelineCalendarEventsFromPersonId( - $personId: UUID! - $page: Int! - $pageSize: Int! - ) { - getTimelineCalendarEventsFromPersonId( - personId: $personId - page: $page - pageSize: $pageSize - ) { - ...TimelineCalendarEventsWithTotalFragment - } - } - ${timelineCalendarEventWithTotalFragment} -`; diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailsCard.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailsCard.tsx index 46e0d6e9be..e41406b141 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailsCard.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailsCard.tsx @@ -7,11 +7,8 @@ import { ComposeEmailButton } from '@/activities/emails/components/ComposeEmailB import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; import { EmptyInboxPlaceholder } from '@/activities/emails/components/EmptyInboxPlaceholder'; import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging'; -import { getTimelineThreadsFromCompanyId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId'; -import { getTimelineThreadsFromOpportunityId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromOpportunityId'; -import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromPersonId'; +import { getTimelineThreadsFromObjectRecord } from '@/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord'; import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { Trans } from '@lingui/react/macro'; import { H1Title, H1TitleFontColor } from 'twenty-ui/typography'; @@ -52,26 +49,17 @@ const StyledEmailCount = styled.span` export const EmailsCard = () => { const targetRecord = useTargetRecord(); - const [query, queryName] = - targetRecord.targetObjectNameSingular === CoreObjectNameSingular.Person - ? [getTimelineThreadsFromPersonId, 'getTimelineThreadsFromPersonId'] - : targetRecord.targetObjectNameSingular === CoreObjectNameSingular.Company - ? [getTimelineThreadsFromCompanyId, 'getTimelineThreadsFromCompanyId'] - : [ - getTimelineThreadsFromOpportunityId, - 'getTimelineThreadsFromOpportunityId', - ]; - const { data, firstQueryLoading, isFetchingMore, fetchMoreRecords } = useCustomResolver( - query, - queryName, + getTimelineThreadsFromObjectRecord, + 'getTimelineThreadsFromObjectRecord', 'timelineThreads', targetRecord, TIMELINE_THREADS_DEFAULT_PAGE_SIZE, ); - const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {}; + const { totalNumberOfThreads, timelineThreads } = + data?.getTimelineThreadsFromObjectRecord ?? {}; const hasMoreTimelineThreads = timelineThreads && totalNumberOfThreads ? timelineThreads?.length < totalNumberOfThreads diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromCompanyId.test.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromCompanyId.test.ts deleted file mode 100644 index 7c09d54871..0000000000 --- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromCompanyId.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { gql } from '@apollo/client'; - -import { getTimelineThreadsFromCompanyId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId'; - -jest.mock('@apollo/client', () => ({ - gql: jest.fn().mockImplementation((strings) => { - return strings.map((str: string) => str.trim()).join(' '); - }), -})); - -describe('getTimelineThreadsFromCompanyId query', () => { - test('should construct the query correctly', () => { - expect(gql).toHaveBeenCalled(); - expect(getTimelineThreadsFromCompanyId).toBeDefined(); - }); -}); diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromPersonId.test.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromPersonId.test.ts deleted file mode 100644 index 1c0b1222d6..0000000000 --- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/__tests__/getTimelineThreadsFromPersonId.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { gql } from '@apollo/client'; - -import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromPersonId'; - -jest.mock('@apollo/client', () => ({ - gql: jest.fn().mockImplementation((strings) => { - return strings.map((str: string) => str.trim()).join(' '); - }), -})); - -describe('getTimelineThreadsFromPersonId query', () => { - test('should construct the query correctly', () => { - expect(gql).toHaveBeenCalled(); - expect(getTimelineThreadsFromPersonId).toBeDefined(); - }); -}); diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId.ts deleted file mode 100644 index e999e676b9..0000000000 --- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment'; -import { gql } from '@apollo/client'; - -export const getTimelineThreadsFromCompanyId = gql` - query GetTimelineThreadsFromCompanyId( - $companyId: UUID! - $page: Int! - $pageSize: Int! - ) { - getTimelineThreadsFromCompanyId( - companyId: $companyId - page: $page - pageSize: $pageSize - ) { - ...TimelineThreadsWithTotalFragment - } - } - ${timelineThreadWithTotalFragment} -`; diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromOpportunityId.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord.ts similarity index 57% rename from packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromOpportunityId.ts rename to packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord.ts index a085623987..569b2b52ee 100644 --- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromOpportunityId.ts +++ b/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord.ts @@ -1,15 +1,16 @@ +import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment'; import { gql } from '@apollo/client'; -import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment'; - -export const getTimelineThreadsFromOpportunityId = gql` - query GetTimelineThreadsFromOpportunityId( - $opportunityId: UUID! +export const getTimelineThreadsFromObjectRecord = gql` + query GetTimelineThreadsFromObjectRecord( + $objectNameSingular: String! + $recordId: UUID! $page: Int! $pageSize: Int! ) { - getTimelineThreadsFromOpportunityId( - opportunityId: $opportunityId + getTimelineThreadsFromObjectRecord( + objectNameSingular: $objectNameSingular + recordId: $recordId page: $page pageSize: $pageSize ) { diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromPersonId.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromPersonId.ts deleted file mode 100644 index 7f1877f112..0000000000 --- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/getTimelineThreadsFromPersonId.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { gql } from '@apollo/client'; - -import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment'; - -export const getTimelineThreadsFromPersonId = gql` - query GetTimelineThreadsFromPersonId( - $personId: UUID! - $page: Int! - $pageSize: Int! - ) { - getTimelineThreadsFromPersonId( - personId: $personId - page: $page - pageSize: $pageSize - ) { - ...TimelineThreadsWithTotalFragment - } - } - ${timelineThreadWithTotalFragment} -`; diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts index 7157cc66ff..e7d8f66435 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts @@ -3,9 +3,7 @@ import { useCallback } from 'react'; import { type EmailAttachment } from 'twenty-shared/types'; import { SEND_EMAIL } from '@/activities/emails/graphql/mutations/sendEmail'; -import { getTimelineThreadsFromCompanyId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId'; -import { getTimelineThreadsFromOpportunityId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromOpportunityId'; -import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromPersonId'; +import { getTimelineThreadsFromObjectRecord } from '@/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { t } from '@lingui/core/macro'; @@ -60,9 +58,7 @@ export const useSendEmail = () => { await apolloCoreClient.refetchQueries({ include: [ - getTimelineThreadsFromCompanyId, - getTimelineThreadsFromPersonId, - getTimelineThreadsFromOpportunityId, + getTimelineThreadsFromObjectRecord, 'FindManyMessages', 'FindManyMessageParticipants', 'FindManyMessageChannelMessageAssociations', diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCustomResolver.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCustomResolver.test.tsx new file mode 100644 index 0000000000..455152b886 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCustomResolver.test.tsx @@ -0,0 +1,57 @@ +import { renderHook } from '@testing-library/react'; + +import { getTimelineThreadsFromObjectRecord } from '@/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord'; +import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; + +jest.mock('@apollo/client/react', () => ({ + useQuery: jest.fn(), +})); + +jest.mock('@/object-metadata/hooks/useApolloCoreClient', () => ({ + useApolloCoreClient: jest.fn(() => ({})), +})); + +jest.mock('@/apollo/hooks/useSnackBarOnQueryError', () => ({ + useSnackBarOnQueryError: jest.fn(), +})); + +const useQueryMock = jest.requireMock('@apollo/client/react').useQuery; + +describe('useCustomResolver', () => { + beforeEach(() => { + useQueryMock.mockReturnValue({ + data: undefined, + loading: false, + fetchMore: jest.fn(), + error: undefined, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('queries the timeline resolver by object name and record id for any object', () => { + renderHook(() => + useCustomResolver( + getTimelineThreadsFromObjectRecord, + 'getTimelineThreadsFromObjectRecord', + 'timelineThreads', + { id: 'record-id', targetObjectNameSingular: 'peopleList' }, + 10, + ), + ); + + expect(useQueryMock).toHaveBeenCalledWith( + getTimelineThreadsFromObjectRecord, + expect.objectContaining({ + variables: { + objectNameSingular: 'peopleList', + recordId: 'record-id', + page: 1, + pageSize: 10, + }, + }), + ); + }); +}); diff --git a/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts index 67d4356b27..f5dd006f70 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts @@ -9,7 +9,6 @@ import { useState } from 'react'; import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; type CustomResolverQueryResult< T extends { @@ -47,13 +46,8 @@ export const useCustomResolver = < const [isFetchingMore, setIsFetchingMore] = useState(false); const queryVariables = { - ...(activityTargetableObject.targetObjectNameSingular === - CoreObjectNameSingular.Person - ? { personId: activityTargetableObject.id } - : activityTargetableObject.targetObjectNameSingular === - CoreObjectNameSingular.Opportunity - ? { opportunityId: activityTargetableObject.id } - : { companyId: activityTargetableObject.id }), + objectNameSingular: activityTargetableObject.targetObjectNameSingular, + recordId: activityTargetableObject.id, page: 1, pageSize, }; diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts index 6017807226..4637a6a0de 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { TimelineCalendarEventResolver } from 'src/engine/core-modules/calendar/timeline-calendar-event.resolver'; import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { RelatedPersonIdsModule } from 'src/engine/core-modules/related-person-ids/related-person-ids.module'; import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { UserModule } from 'src/engine/core-modules/user/user.module'; @@ -11,6 +12,7 @@ import { UserModule } from 'src/engine/core-modules/user/user.module'; @Module({ imports: [ UserModule, + RelatedPersonIdsModule, TypeOrmModule.forFeature([ CalendarChannelEntity, ConnectedAccountEntity, diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts index b572b1f331..5cefc7c909 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts @@ -2,6 +2,7 @@ import { UseGuards } from '@nestjs/common'; import { Args, ArgsType, Field, Int, Query } from '@nestjs/graphql'; import { Max } from 'class-validator'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; @@ -14,6 +15,22 @@ import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +@ArgsType() +class GetTimelineCalendarEventsFromObjectRecordArgs { + @Field(() => String) + objectNameSingular: string; + + @Field(() => UUIDScalarType) + recordId: string; + + @Field(() => Int) + page: number; + + @Field(() => Int) + @Max(TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE) + pageSize: number; +} + @ArgsType() class GetTimelineCalendarEventsFromPersonIdArgs { @Field(() => UUIDScalarType) @@ -61,44 +78,68 @@ export class TimelineCalendarEventResolver { ) {} @Query(() => TimelineCalendarEventsWithTotalDTO) + async getTimelineCalendarEventsFromObjectRecord( + @Args() + { + objectNameSingular, + recordId, + page, + pageSize, + }: GetTimelineCalendarEventsFromObjectRecordArgs, + @AuthWorkspaceMemberId() workspaceMemberId: string, + @AuthWorkspace() workspace: WorkspaceEntity, + ) { + return this.timelineCalendarEventService.getCalendarEventsFromObjectRecord({ + currentWorkspaceMemberId: workspaceMemberId, + objectNameSingular, + recordId, + workspaceId: workspace.id, + page, + pageSize, + }); + } + + @Query(() => TimelineCalendarEventsWithTotalDTO, { + deprecationReason: 'Use getTimelineCalendarEventsFromObjectRecord instead', + }) async getTimelineCalendarEventsFromPersonId( @Args() { personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs, @AuthWorkspaceMemberId() workspaceMemberId: string, @AuthWorkspace() workspace: WorkspaceEntity, ) { - const timelineCalendarEvents = - await this.timelineCalendarEventService.getCalendarEventsFromPersonIds({ - currentWorkspaceMemberId: workspaceMemberId, - personIds: [personId], - workspaceId: workspace.id, - page, - pageSize, - }); - - return timelineCalendarEvents; + return this.timelineCalendarEventService.getCalendarEventsFromObjectRecord({ + currentWorkspaceMemberId: workspaceMemberId, + objectNameSingular: CoreObjectNameSingular.Person, + recordId: personId, + workspaceId: workspace.id, + page, + pageSize, + }); } - @Query(() => TimelineCalendarEventsWithTotalDTO) + @Query(() => TimelineCalendarEventsWithTotalDTO, { + deprecationReason: 'Use getTimelineCalendarEventsFromObjectRecord instead', + }) async getTimelineCalendarEventsFromCompanyId( @Args() { companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs, @AuthWorkspaceMemberId() workspaceMemberId: string, @AuthWorkspace() workspace: WorkspaceEntity, ) { - const timelineCalendarEvents = - await this.timelineCalendarEventService.getCalendarEventsFromCompanyId({ - currentWorkspaceMemberId: workspaceMemberId, - companyId, - workspaceId: workspace.id, - page, - pageSize, - }); - - return timelineCalendarEvents; + return this.timelineCalendarEventService.getCalendarEventsFromObjectRecord({ + currentWorkspaceMemberId: workspaceMemberId, + objectNameSingular: CoreObjectNameSingular.Company, + recordId: companyId, + workspaceId: workspace.id, + page, + pageSize, + }); } - @Query(() => TimelineCalendarEventsWithTotalDTO) + @Query(() => TimelineCalendarEventsWithTotalDTO, { + deprecationReason: 'Use getTimelineCalendarEventsFromObjectRecord instead', + }) async getTimelineCalendarEventsFromOpportunityId( @Args() { @@ -109,17 +150,13 @@ export class TimelineCalendarEventResolver { @AuthWorkspaceMemberId() workspaceMemberId: string, @AuthWorkspace() workspace: WorkspaceEntity, ) { - const timelineCalendarEvents = - await this.timelineCalendarEventService.getCalendarEventsFromOpportunityId( - { - currentWorkspaceMemberId: workspaceMemberId, - opportunityId, - workspaceId: workspace.id, - page, - pageSize, - }, - ); - - return timelineCalendarEvents; + return this.timelineCalendarEventService.getCalendarEventsFromObjectRecord({ + currentWorkspaceMemberId: workspaceMemberId, + objectNameSingular: CoreObjectNameSingular.Opportunity, + recordId: opportunityId, + workspaceId: workspace.id, + page, + pageSize, + }); } } diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts index 6345d46e79..b6a50caea6 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts @@ -5,6 +5,7 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/ import { CalendarChannelVisibility } from 'twenty-shared/types'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { RelatedPersonIdsService } from 'src/engine/core-modules/related-person-ids/services/related-person-ids.service'; import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; @@ -96,6 +97,10 @@ describe('TimelineCalendarEventService', () => { provide: getRepositoryToken(UserWorkspaceEntity), useValue: mockUserWorkspaceRepository, }, + { + provide: RelatedPersonIdsService, + useValue: { getRelatedPersonIds: jest.fn().mockResolvedValue([]) }, + }, ], }).compile(); diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts index c384aaefe5..c794a638e6 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts @@ -8,14 +8,13 @@ import { Any, In, type Repository } from 'typeorm'; import { CalendarChannelVisibility } from 'twenty-shared/types'; import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; import { type TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto'; +import { RelatedPersonIdsService } from 'src/engine/core-modules/related-person-ids/services/related-person-ids.service'; import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; -import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; -import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() @@ -28,6 +27,7 @@ export class TimelineCalendarEventService { private readonly connectedAccountRepository: Repository, @InjectRepository(UserWorkspaceEntity) private readonly userWorkspaceRepository: Repository, + private readonly relatedPersonIdsService: RelatedPersonIdsService, ) {} async getCalendarEventsFromPersonIds({ @@ -259,113 +259,40 @@ export class TimelineCalendarEventService { ); } - async getCalendarEventsFromCompanyId({ + async getCalendarEventsFromObjectRecord({ currentWorkspaceMemberId, - companyId, + objectNameSingular, + recordId, workspaceId, page = 1, pageSize = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, }: { currentWorkspaceMemberId: string; - companyId: string; + objectNameSingular: string; + recordId: string; workspaceId: string; page: number; pageSize: number; }): Promise { - const authContext = buildSystemAuthContext(workspaceId); + const personIds = await this.relatedPersonIdsService.getRelatedPersonIds({ + workspaceId, + objectNameSingular, + recordId, + }); - return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - const personRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'person', - { shouldBypassPermissionChecks: true }, - ); + if (personIds.length === 0) { + return { + totalNumberOfCalendarEvents: 0, + timelineCalendarEvents: [], + }; + } - const personIds = await personRepository.find({ - where: { - companyId, - }, - select: { - id: true, - }, - }); - - if (personIds.length <= 0) { - return { - totalNumberOfCalendarEvents: 0, - timelineCalendarEvents: [], - }; - } - - const formattedPersonIds = personIds.map(({ id }) => id); - - const calendarEvents = await this.getCalendarEventsFromPersonIds({ - currentWorkspaceMemberId, - personIds: formattedPersonIds, - workspaceId, - page, - pageSize, - }); - - return calendarEvents; - }, - authContext, - ); - } - - async getCalendarEventsFromOpportunityId({ - currentWorkspaceMemberId, - opportunityId, - workspaceId, - page = 1, - pageSize = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, - }: { - currentWorkspaceMemberId: string; - opportunityId: string; - workspaceId: string; - page: number; - pageSize: number; - }): Promise { - const authContext = buildSystemAuthContext(workspaceId); - - return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - const opportunityRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'opportunity', - { shouldBypassPermissionChecks: true }, - ); - - const opportunity = await opportunityRepository.findOne({ - where: { - id: opportunityId, - }, - select: { - companyId: true, - }, - }); - - if (!opportunity?.companyId) { - return { - totalNumberOfCalendarEvents: 0, - timelineCalendarEvents: [], - }; - } - - const calendarEvents = await this.getCalendarEventsFromCompanyId({ - currentWorkspaceMemberId, - companyId: opportunity.companyId, - workspaceId, - page, - pageSize, - }); - - return calendarEvents; - }, - authContext, - ); + return this.getCalendarEventsFromPersonIds({ + currentWorkspaceMemberId, + personIds, + workspaceId, + page, + pageSize, + }); } } diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts index 9967ae8e2c..c6c3212088 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts @@ -4,16 +4,13 @@ import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/mess import { type TimelineThreadsWithTotalDTO } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto'; import { TimelineMessagingService } from 'src/engine/core-modules/messaging/services/timeline-messaging.service'; import { formatThreads } from 'src/engine/core-modules/messaging/utils/format-threads.util'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; -import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; +import { RelatedPersonIdsService } from 'src/engine/core-modules/related-person-ids/services/related-person-ids.service'; @Injectable() export class GetMessagesService { constructor( - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly timelineMessagingService: TimelineMessagingService, + private readonly relatedPersonIdsService: RelatedPersonIdsService, ) {} async getMessagesFromPersonIds( @@ -67,100 +64,33 @@ export class GetMessagesService { }; } - async getMessagesFromCompanyId( + async getMessagesFromObjectRecord( workspaceMemberId: string, - companyId: string, + objectNameSingular: string, + recordId: string, workspaceId: string, page = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, ): Promise { - const authContext = buildSystemAuthContext(workspaceId); + const personIds = await this.relatedPersonIdsService.getRelatedPersonIds({ + workspaceId, + objectNameSingular, + recordId, + }); - return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - const personRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'person', - { shouldBypassPermissionChecks: true }, - ); - const personIds = ( - await personRepository.find({ - where: { - companyId, - }, - select: { - id: true, - }, - }) - ).map((person) => person.id); + if (personIds.length === 0) { + return { + totalNumberOfThreads: 0, + timelineThreads: [], + }; + } - if (personIds.length === 0) { - return { - totalNumberOfThreads: 0, - timelineThreads: [], - }; - } - - const messageThreads = await this.getMessagesFromPersonIds( - workspaceMemberId, - personIds, - workspaceId, - page, - pageSize, - ); - - return messageThreads; - }, - authContext, - ); - } - - async getMessagesFromOpportunityId( - workspaceMemberId: string, - opportunityId: string, - workspaceId: string, - page = 1, - pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, - ): Promise { - const authContext = buildSystemAuthContext(workspaceId); - - return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - const opportunityRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'opportunity', - { shouldBypassPermissionChecks: true }, - ); - - const opportunity = await opportunityRepository.findOne({ - where: { - id: opportunityId, - }, - select: { - companyId: true, - }, - }); - - if (!opportunity?.companyId) { - return { - totalNumberOfThreads: 0, - timelineThreads: [], - }; - } - - const messageThreads = await this.getMessagesFromCompanyId( - workspaceMemberId, - opportunity.companyId, - workspaceId, - page, - pageSize, - ); - - return messageThreads; - }, - authContext, + return this.getMessagesFromPersonIds( + workspaceMemberId, + personIds, + workspaceId, + page, + pageSize, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts index 579aaefdcc..c2692a1082 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { GetMessagesService } from 'src/engine/core-modules/messaging/services/get-messages.service'; import { TimelineMessagingService } from 'src/engine/core-modules/messaging/services/timeline-messaging.service'; import { TimelineMessagingResolver } from 'src/engine/core-modules/messaging/timeline-messaging.resolver'; +import { RelatedPersonIdsModule } from 'src/engine/core-modules/related-person-ids/related-person-ids.module'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserModule } from 'src/engine/core-modules/user/user.module'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @@ -20,6 +21,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature- ConnectedAccountModule, FeatureFlagModule, PermissionsModule, + RelatedPersonIdsModule, TypeOrmModule.forFeature([ MessageChannelEntity, ConnectedAccountEntity, diff --git a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts index 1f38154119..91450fabd2 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts @@ -3,6 +3,7 @@ import { Args, ArgsType, Field, Int, Mutation, Query } from '@nestjs/graphql'; import { Max } from 'class-validator'; import { PermissionFlagType } from 'twenty-shared/constants'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { CoreResolver } from 'src/engine/api/graphql/graphql-config/decorators/core-resolver.decorator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @@ -21,6 +22,22 @@ import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.g import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; +@ArgsType() +class GetTimelineThreadsFromObjectRecordArgs { + @Field(() => String) + objectNameSingular: string; + + @Field(() => UUIDScalarType) + recordId: string; + + @Field(() => Int) + page: number; + + @Field(() => Int) + @Max(TIMELINE_THREADS_MAX_PAGE_SIZE) + pageSize: number; +} + @ArgsType() class GetTimelineThreadsFromPersonIdArgs { @Field(() => UUIDScalarType) @@ -70,6 +87,39 @@ export class TimelineMessagingResolver { ) {} @Query(() => TimelineThreadsWithTotalDTO) + async getTimelineThreadsFromObjectRecord( + @AuthUser() user: AuthContextUser, + @AuthWorkspace() workspace: WorkspaceEntity, + @Args() + { + objectNameSingular, + recordId, + page, + pageSize, + }: GetTimelineThreadsFromObjectRecordArgs, + ) { + const workspaceMember = await this.userService.loadWorkspaceMember( + user, + workspace, + ); + + if (!workspaceMember) { + return; + } + + return this.getMessagesFromPersonIdsService.getMessagesFromObjectRecord( + workspaceMember.id, + objectNameSingular, + recordId, + workspace.id, + page, + pageSize, + ); + } + + @Query(() => TimelineThreadsWithTotalDTO, { + deprecationReason: 'Use getTimelineThreadsFromObjectRecord instead', + }) async getTimelineThreadsFromPersonId( @AuthUser() user: AuthContextUser, @AuthWorkspace() workspace: WorkspaceEntity, @@ -84,19 +134,19 @@ export class TimelineMessagingResolver { return; } - const timelineThreads = - await this.getMessagesFromPersonIdsService.getMessagesFromPersonIds( - workspaceMember.id, - [personId], - workspace.id, - page, - pageSize, - ); - - return timelineThreads; + return this.getMessagesFromPersonIdsService.getMessagesFromObjectRecord( + workspaceMember.id, + CoreObjectNameSingular.Person, + personId, + workspace.id, + page, + pageSize, + ); } - @Query(() => TimelineThreadsWithTotalDTO) + @Query(() => TimelineThreadsWithTotalDTO, { + deprecationReason: 'Use getTimelineThreadsFromObjectRecord instead', + }) async getTimelineThreadsFromCompanyId( @AuthUser() user: AuthContextUser, @AuthWorkspace() workspace: WorkspaceEntity, @@ -111,19 +161,19 @@ export class TimelineMessagingResolver { return; } - const timelineThreads = - await this.getMessagesFromPersonIdsService.getMessagesFromCompanyId( - workspaceMember.id, - companyId, - workspace.id, - page, - pageSize, - ); - - return timelineThreads; + return this.getMessagesFromPersonIdsService.getMessagesFromObjectRecord( + workspaceMember.id, + CoreObjectNameSingular.Company, + companyId, + workspace.id, + page, + pageSize, + ); } - @Query(() => TimelineThreadsWithTotalDTO) + @Query(() => TimelineThreadsWithTotalDTO, { + deprecationReason: 'Use getTimelineThreadsFromObjectRecord instead', + }) async getTimelineThreadsFromOpportunityId( @AuthUser() user: AuthContextUser, @AuthWorkspace() workspace: WorkspaceEntity, @@ -139,16 +189,14 @@ export class TimelineMessagingResolver { return; } - const timelineThreads = - await this.getMessagesFromPersonIdsService.getMessagesFromOpportunityId( - workspaceMember.id, - opportunityId, - workspace.id, - page, - pageSize, - ); - - return timelineThreads; + return this.getMessagesFromPersonIdsService.getMessagesFromObjectRecord( + workspaceMember.id, + CoreObjectNameSingular.Opportunity, + opportunityId, + workspace.id, + page, + pageSize, + ); } @UseGuards(SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS)) diff --git a/packages/twenty-server/src/engine/core-modules/related-person-ids/related-person-ids.module.ts b/packages/twenty-server/src/engine/core-modules/related-person-ids/related-person-ids.module.ts new file mode 100644 index 0000000000..3277014709 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/related-person-ids/related-person-ids.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { RelatedPersonIdsService } from 'src/engine/core-modules/related-person-ids/services/related-person-ids.service'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; + +@Module({ + imports: [WorkspaceCacheModule], + providers: [RelatedPersonIdsService], + exports: [RelatedPersonIdsService], +}) +export class RelatedPersonIdsModule {} diff --git a/packages/twenty-server/src/engine/core-modules/related-person-ids/services/related-person-ids.service.ts b/packages/twenty-server/src/engine/core-modules/related-person-ids/services/related-person-ids.service.ts new file mode 100644 index 0000000000..b71da64ee4 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/related-person-ids/services/related-person-ids.service.ts @@ -0,0 +1,130 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined } from 'twenty-shared/utils'; +import { In, type FindOptionsSelect, type FindOptionsWhere } from 'typeorm'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { + findRelationPathsToPerson, + type RelationPathToPerson, +} from 'src/engine/core-modules/related-person-ids/utils/find-relation-paths-to-person.util'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; + +const PERSON_OBJECT_NAME_SINGULAR = 'person'; + +type RelationWalkRecord = { id: string } & Record; + +@Injectable() +export class RelatedPersonIdsService { + constructor( + private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, + private readonly workspaceCacheService: WorkspaceCacheService, + ) {} + + async getRelatedPersonIds({ + workspaceId, + objectNameSingular, + recordId, + }: { + workspaceId: string; + objectNameSingular: string; + recordId: string; + }): Promise { + if (objectNameSingular === PERSON_OBJECT_NAME_SINGULAR) { + return [recordId]; + } + + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const relationPaths = findRelationPathsToPerson({ + rootObjectNameSingular: objectNameSingular, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }); + + if (relationPaths.length === 0) { + return []; + } + + return this.globalWorkspaceOrmManager.executeInWorkspaceContext( + async () => { + const personIds = new Set(); + + for (const relationPath of relationPaths) { + const personIdsForPath = await this.walkRelationPath({ + workspaceId, + recordId, + relationPath, + }); + + personIdsForPath.forEach((personId) => personIds.add(personId)); + } + + return [...personIds]; + }, + buildSystemAuthContext(workspaceId), + ); + } + + private async walkRelationPath({ + workspaceId, + recordId, + relationPath, + }: { + workspaceId: string; + recordId: string; + relationPath: RelationPathToPerson; + }): Promise { + let currentIds = [recordId]; + + for (const hop of relationPath) { + if (currentIds.length === 0) { + return []; + } + + const repository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + hop.queryObjectNameSingular, + { shouldBypassPermissionChecks: true }, + ); + + if (hop.direction === RelationType.MANY_TO_ONE) { + const records = await repository.find({ + where: { id: In(currentIds) } as FindOptionsWhere, + select: { + [hop.joinColumnName]: true, + } as FindOptionsSelect, + }); + + currentIds = [ + ...new Set( + records + .map((record) => record[hop.joinColumnName]) + .filter( + (value): value is string => + typeof value === 'string' && isDefined(value), + ), + ), + ]; + } else { + const records = await repository.find({ + where: { + [hop.joinColumnName]: In(currentIds), + } as FindOptionsWhere, + select: { id: true }, + }); + + currentIds = [...new Set(records.map((record) => record.id))]; + } + } + + return currentIds; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/__tests__/find-relation-paths-to-person.util.spec.ts b/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/__tests__/find-relation-paths-to-person.util.spec.ts new file mode 100644 index 0000000000..04717ee5b6 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/__tests__/find-relation-paths-to-person.util.spec.ts @@ -0,0 +1,294 @@ +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { resolveRelationFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-relation-from-flat-field-metadata.util'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { findRelationPathsToPerson } from 'src/engine/core-modules/related-person-ids/utils/find-relation-paths-to-person.util'; + +jest.mock( + 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-relation-from-flat-field-metadata.util', +); + +const resolveRelationMock = jest.mocked(resolveRelationFromFlatFieldMetadata); + +type RelationSpec = { + fieldName: string; + relationType: RelationType; + targetObjectNameSingular: string; + inverseFieldName: string; +}; + +const buildGraphFixtures = (graph: Record) => { + const fieldId = (objectNameSingular: string, fieldName: string) => + `${objectNameSingular}.${fieldName}`; + + const flatObjectMetadataMaps = { + byUniversalIdentifier: {}, + universalIdentifierById: {}, + } as unknown as FlatEntityMaps; + + const flatFieldMetadataMaps = { + byUniversalIdentifier: {}, + universalIdentifierById: {}, + } as unknown as FlatEntityMaps; + + const relationSpecByFieldId = new Map< + string, + { sourceObjectNameSingular: string; spec: RelationSpec } + >(); + + for (const [objectNameSingular, relationSpecs] of Object.entries(graph)) { + flatObjectMetadataMaps.byUniversalIdentifier[objectNameSingular] = { + id: objectNameSingular, + nameSingular: objectNameSingular, + fieldIds: relationSpecs.map((spec) => + fieldId(objectNameSingular, spec.fieldName), + ), + } as unknown as FlatObjectMetadata; + flatObjectMetadataMaps.universalIdentifierById[objectNameSingular] = + objectNameSingular; + + for (const spec of relationSpecs) { + const id = fieldId(objectNameSingular, spec.fieldName); + + flatFieldMetadataMaps.byUniversalIdentifier[id] = { + id, + type: 'RELATION', + } as unknown as FlatFieldMetadata; + flatFieldMetadataMaps.universalIdentifierById[id] = id; + + relationSpecByFieldId.set(id, { + sourceObjectNameSingular: objectNameSingular, + spec, + }); + } + } + + resolveRelationMock.mockImplementation(({ sourceFlatFieldMetadata }) => { + const resolved = relationSpecByFieldId.get(sourceFlatFieldMetadata.id); + + if (resolved === undefined) { + return null; + } + + const { sourceObjectNameSingular, spec } = resolved; + + return { + type: spec.relationType, + sourceObjectMetadata: { + id: sourceObjectNameSingular, + nameSingular: sourceObjectNameSingular, + }, + targetObjectMetadata: { + id: spec.targetObjectNameSingular, + nameSingular: spec.targetObjectNameSingular, + }, + sourceFieldMetadata: { name: spec.fieldName }, + targetFieldMetadata: { name: spec.inverseFieldName }, + } as ReturnType; + }); + + return { flatObjectMetadataMaps, flatFieldMetadataMaps }; +}; + +afterEach(() => { + resolveRelationMock.mockReset(); +}); + +describe('findRelationPathsToPerson', () => { + it('returns the empty path for the person object itself', () => { + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + buildGraphFixtures({ person: [] }); + + expect( + findRelationPathsToPerson({ + rootObjectNameSingular: 'person', + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }), + ).toEqual([[]]); + }); + + it('resolves a direct relation to person, querying person by its foreign key', () => { + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + buildGraphFixtures({ + company: [ + { + fieldName: 'people', + relationType: RelationType.ONE_TO_MANY, + targetObjectNameSingular: 'person', + inverseFieldName: 'company', + }, + ], + person: [ + { + fieldName: 'company', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'company', + inverseFieldName: 'people', + }, + ], + }); + + expect( + findRelationPathsToPerson({ + rootObjectNameSingular: 'company', + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }), + ).toEqual([ + [ + { + direction: RelationType.ONE_TO_MANY, + queryObjectNameSingular: 'person', + joinColumnName: 'companyId', + }, + ], + ]); + }); + + it('resolves person through a two-hop join object', () => { + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + buildGraphFixtures({ + peopleList: [ + { + fieldName: 'peopleListMemberships', + relationType: RelationType.ONE_TO_MANY, + targetObjectNameSingular: 'peopleListMembership', + inverseFieldName: 'peopleList', + }, + ], + peopleListMembership: [ + { + fieldName: 'peopleList', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'peopleList', + inverseFieldName: 'peopleListMemberships', + }, + { + fieldName: 'person', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'person', + inverseFieldName: 'peopleListMemberships', + }, + ], + person: [ + { + fieldName: 'peopleListMemberships', + relationType: RelationType.ONE_TO_MANY, + targetObjectNameSingular: 'peopleListMembership', + inverseFieldName: 'person', + }, + ], + }); + + expect( + findRelationPathsToPerson({ + rootObjectNameSingular: 'peopleList', + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }), + ).toEqual([ + [ + { + direction: RelationType.ONE_TO_MANY, + queryObjectNameSingular: 'peopleListMembership', + joinColumnName: 'peopleListId', + }, + { + direction: RelationType.MANY_TO_ONE, + queryObjectNameSingular: 'peopleListMembership', + joinColumnName: 'personId', + }, + ], + ]); + }); + + it('collects both a direct and a longer relation chain to person (opportunity)', () => { + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + buildGraphFixtures({ + opportunity: [ + { + fieldName: 'pointOfContact', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'person', + inverseFieldName: 'pointOfContactForOpportunities', + }, + { + fieldName: 'company', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'company', + inverseFieldName: 'opportunities', + }, + ], + company: [ + { + fieldName: 'people', + relationType: RelationType.ONE_TO_MANY, + targetObjectNameSingular: 'person', + inverseFieldName: 'company', + }, + ], + person: [], + }); + + expect( + findRelationPathsToPerson({ + rootObjectNameSingular: 'opportunity', + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }), + ).toEqual([ + [ + { + direction: RelationType.MANY_TO_ONE, + queryObjectNameSingular: 'opportunity', + joinColumnName: 'pointOfContactId', + }, + ], + [ + { + direction: RelationType.MANY_TO_ONE, + queryObjectNameSingular: 'opportunity', + joinColumnName: 'companyId', + }, + { + direction: RelationType.ONE_TO_MANY, + queryObjectNameSingular: 'person', + joinColumnName: 'companyId', + }, + ], + ]); + }); + + it('returns no path when person is unreachable, terminating on relation cycles', () => { + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + buildGraphFixtures({ + rocket: [ + { + fieldName: 'cells', + relationType: RelationType.ONE_TO_MANY, + targetObjectNameSingular: 'rocketCell', + inverseFieldName: 'rocket', + }, + ], + rocketCell: [ + { + fieldName: 'rocket', + relationType: RelationType.MANY_TO_ONE, + targetObjectNameSingular: 'rocket', + inverseFieldName: 'cells', + }, + ], + person: [], + }); + + expect( + findRelationPathsToPerson({ + rootObjectNameSingular: 'rocket', + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }), + ).toEqual([]); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/find-relation-paths-to-person.util.ts b/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/find-relation-paths-to-person.util.ts new file mode 100644 index 0000000000..0cc45716c2 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/related-person-ids/utils/find-relation-paths-to-person.util.ts @@ -0,0 +1,125 @@ +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; +import { resolveRelationFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-relation-from-flat-field-metadata.util'; +import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; + +const PERSON_OBJECT_NAME_SINGULAR = 'person'; +const DEFAULT_MAX_RELATION_DEPTH_TO_PERSON = 3; + +export type RelationHopToPerson = { + direction: RelationType; + queryObjectNameSingular: string; + joinColumnName: string; +}; + +export type RelationPathToPerson = RelationHopToPerson[]; + +export const findRelationPathsToPerson = ({ + rootObjectNameSingular, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + maxDepth = DEFAULT_MAX_RELATION_DEPTH_TO_PERSON, +}: { + rootObjectNameSingular: string; + flatObjectMetadataMaps: FlatEntityMaps; + flatFieldMetadataMaps: FlatEntityMaps; + maxDepth?: number; +}): RelationPathToPerson[] => { + if (rootObjectNameSingular === PERSON_OBJECT_NAME_SINGULAR) { + return [[]]; + } + + const { idByNameSingular } = buildObjectIdByNameMaps(flatObjectMetadataMaps); + const rootObjectId = idByNameSingular[rootObjectNameSingular]; + + if (!isDefined(rootObjectId)) { + return []; + } + + let frontier = [{ objectId: rootObjectId, path: [] as RelationPathToPerson }]; + const visitedObjectIds = new Set([rootObjectId]); + const pathsToPerson: RelationPathToPerson[] = []; + + for (let depth = 0; depth < maxDepth; depth++) { + const nextFrontier: typeof frontier = []; + const objectIdsReachedThisDepth = new Set(); + + for (const { objectId, path } of frontier) { + const sourceObject = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: objectId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + if (!isDefined(sourceObject)) { + continue; + } + + for (const field of getFlatFieldsFromFlatObjectMetadata( + sourceObject, + flatFieldMetadataMaps, + )) { + if (!isFlatFieldMetadataOfType(field, FieldMetadataType.RELATION)) { + continue; + } + + const relation = resolveRelationFromFlatFieldMetadata({ + sourceFlatFieldMetadata: field, + flatFieldMetadataMaps, + flatObjectMetadataMaps, + }); + + if (!isDefined(relation)) { + continue; + } + + const joinColumnOwner = + relation.type === RelationType.MANY_TO_ONE + ? { + object: relation.sourceObjectMetadata, + field: relation.sourceFieldMetadata, + } + : { + object: relation.targetObjectMetadata, + field: relation.targetFieldMetadata, + }; + + const nextPath: RelationPathToPerson = [ + ...path, + { + direction: relation.type, + queryObjectNameSingular: joinColumnOwner.object.nameSingular, + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: joinColumnOwner.field.name, + }), + }, + ]; + + const targetObjectId = relation.targetObjectMetadata.id; + + if ( + relation.targetObjectMetadata.nameSingular === + PERSON_OBJECT_NAME_SINGULAR + ) { + pathsToPerson.push(nextPath); + } else if (!visitedObjectIds.has(targetObjectId)) { + objectIdsReachedThisDepth.add(targetObjectId); + nextFrontier.push({ objectId: targetObjectId, path: nextPath }); + } + } + } + + objectIdsReachedThisDepth.forEach((id) => visitedObjectIds.add(id)); + frontier = nextFrontier; + } + + return pathsToPerson; +}; diff --git a/packages/twenty-server/test/integration/graphql/suites/timeline-from-object-record.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/timeline-from-object-record.integration-spec.ts new file mode 100644 index 0000000000..0fd34c0293 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/timeline-from-object-record.integration-spec.ts @@ -0,0 +1,396 @@ +import gql from 'graphql-tag'; +import { type DocumentNode } from 'graphql'; +import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util'; +import { findManyOperationFactory } from 'test/integration/graphql/utils/find-many-operation-factory.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; + +const PAGE_SIZE = 50; +const PEOPLE_DISCOVERY_LIMIT = 100; + +const GET_TIMELINE_THREADS = gql` + query GetTimelineThreadsFromObjectRecord( + $objectNameSingular: String! + $recordId: UUID! + $page: Int! + $pageSize: Int! + ) { + getTimelineThreadsFromObjectRecord( + objectNameSingular: $objectNameSingular + recordId: $recordId + page: $page + pageSize: $pageSize + ) { + totalNumberOfThreads + timelineThreads { + id + } + } + } +`; + +const GET_TIMELINE_CALENDAR_EVENTS = gql` + query GetTimelineCalendarEventsFromObjectRecord( + $objectNameSingular: String! + $recordId: UUID! + $page: Int! + $pageSize: Int! + ) { + getTimelineCalendarEventsFromObjectRecord( + objectNameSingular: $objectNameSingular + recordId: $recordId + page: $page + pageSize: $pageSize + ) { + totalNumberOfCalendarEvents + timelineCalendarEvents { + id + } + } + } +`; + +const THREADS_SELECTION = 'totalNumberOfThreads timelineThreads { id }'; +const CALENDAR_SELECTION = + 'totalNumberOfCalendarEvents timelineCalendarEvents { id }'; + +const buildLegacyQuery = ( + queryName: string, + idArgName: string, + selection: string, +): DocumentNode => + gql` + query ${queryName}($id: UUID!, $page: Int!, $pageSize: Int!) { + ${queryName}(${idArgName}: $id, page: $page, pageSize: $pageSize) { + ${selection} + } + } + `; + +const requestTimeline = ( + query: DocumentNode, + objectNameSingular: string, + recordId: string, +) => + makeGraphqlAPIRequest({ + query, + variables: { objectNameSingular, recordId, page: 1, pageSize: PAGE_SIZE }, + }); + +const getPeopleWithCompany = async (): Promise< + { id: string; companyId: string }[] +> => { + const response = await makeGraphqlAPIRequest( + findManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: 'id company { id }', + first: PEOPLE_DISCOVERY_LIMIT, + }), + ); + + expect(response.body.errors).toBeUndefined(); + + return ( + response.body.data.people.edges + // @ts-expect-error legacy noImplicitAny + .map((edge) => edge.node) + // @ts-expect-error legacy noImplicitAny + .filter((person) => person.company?.id) + // @ts-expect-error legacy noImplicitAny + .map((person) => ({ id: person.id, companyId: person.company.id })) + ); +}; + +const findPersonWithThreads = async ( + people: { id: string; companyId: string }[], +) => { + for (const person of people) { + const response = await requestTimeline( + GET_TIMELINE_THREADS, + 'person', + person.id, + ); + + if ( + response.body.data.getTimelineThreadsFromObjectRecord + .totalNumberOfThreads > 0 + ) { + return person; + } + } + + return undefined; +}; + +const findPersonWithCalendarEvents = async ( + people: { id: string; companyId: string }[], +) => { + for (const person of people) { + const response = await requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + 'person', + person.id, + ); + + if ( + response.body.data.getTimelineCalendarEventsFromObjectRecord + .totalNumberOfCalendarEvents > 0 + ) { + return person; + } + } + + return undefined; +}; + +describe('timeline from object record resolvers (integration)', () => { + let personWithThreads: { id: string; companyId: string }; + let personWithEvents: { id: string; companyId: string }; + + beforeAll(async () => { + const people = await getPeopleWithCompany(); + + const threadsSource = await findPersonWithThreads(people); + const eventsSource = await findPersonWithCalendarEvents(people); + + if (!threadsSource || !eventsSource) { + throw new Error( + 'Expected the seeded workspace to contain a person with message threads and calendar events', + ); + } + + personWithThreads = threadsSource; + personWithEvents = eventsSource; + }); + + it('should derive a company message timeline from its related people', async () => { + const [personResponse, companyResponse] = await Promise.all([ + requestTimeline(GET_TIMELINE_THREADS, 'person', personWithThreads.id), + requestTimeline( + GET_TIMELINE_THREADS, + 'company', + personWithThreads.companyId, + ), + ]); + + expect(personResponse.body.errors).toBeUndefined(); + expect(companyResponse.body.errors).toBeUndefined(); + + const personThreadCount = + personResponse.body.data.getTimelineThreadsFromObjectRecord + .totalNumberOfThreads; + const companyThreadCount = + companyResponse.body.data.getTimelineThreadsFromObjectRecord + .totalNumberOfThreads; + + expect(personThreadCount).toBeGreaterThan(0); + expect(companyThreadCount).toBeGreaterThanOrEqual(personThreadCount); + }); + + it('should derive a company calendar timeline from its related people', async () => { + const [personResponse, companyResponse] = await Promise.all([ + requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + 'person', + personWithEvents.id, + ), + requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + 'company', + personWithEvents.companyId, + ), + ]); + + expect(personResponse.body.errors).toBeUndefined(); + expect(companyResponse.body.errors).toBeUndefined(); + + const personEventCount = + personResponse.body.data.getTimelineCalendarEventsFromObjectRecord + .totalNumberOfCalendarEvents; + const companyEventCount = + companyResponse.body.data.getTimelineCalendarEventsFromObjectRecord + .totalNumberOfCalendarEvents; + + expect(personEventCount).toBeGreaterThan(0); + expect(companyEventCount).toBeGreaterThanOrEqual(personEventCount); + }); + + it('should serve getTimelineThreadsFromPersonId identically to the object-record resolver', async () => { + const [fromObjectRecord, fromLegacy] = await Promise.all([ + requestTimeline(GET_TIMELINE_THREADS, 'person', personWithThreads.id), + makeGraphqlAPIRequest({ + query: buildLegacyQuery( + 'getTimelineThreadsFromPersonId', + 'personId', + THREADS_SELECTION, + ), + variables: { id: personWithThreads.id, page: 1, pageSize: PAGE_SIZE }, + }), + ]); + + expect(fromLegacy.body.errors).toBeUndefined(); + expect(fromLegacy.body.data.getTimelineThreadsFromPersonId).toEqual( + fromObjectRecord.body.data.getTimelineThreadsFromObjectRecord, + ); + }); + + it('should serve getTimelineThreadsFromCompanyId identically to the object-record resolver', async () => { + const [fromObjectRecord, fromLegacy] = await Promise.all([ + requestTimeline( + GET_TIMELINE_THREADS, + 'company', + personWithThreads.companyId, + ), + makeGraphqlAPIRequest({ + query: buildLegacyQuery( + 'getTimelineThreadsFromCompanyId', + 'companyId', + THREADS_SELECTION, + ), + variables: { + id: personWithThreads.companyId, + page: 1, + pageSize: PAGE_SIZE, + }, + }), + ]); + + expect(fromLegacy.body.errors).toBeUndefined(); + expect(fromLegacy.body.data.getTimelineThreadsFromCompanyId).toEqual( + fromObjectRecord.body.data.getTimelineThreadsFromObjectRecord, + ); + }); + + it('should serve getTimelineCalendarEventsFromPersonId identically to the object-record resolver', async () => { + const [fromObjectRecord, fromLegacy] = await Promise.all([ + requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + 'person', + personWithEvents.id, + ), + makeGraphqlAPIRequest({ + query: buildLegacyQuery( + 'getTimelineCalendarEventsFromPersonId', + 'personId', + CALENDAR_SELECTION, + ), + variables: { id: personWithEvents.id, page: 1, pageSize: PAGE_SIZE }, + }), + ]); + + expect(fromLegacy.body.errors).toBeUndefined(); + expect(fromLegacy.body.data.getTimelineCalendarEventsFromPersonId).toEqual( + fromObjectRecord.body.data.getTimelineCalendarEventsFromObjectRecord, + ); + }); + + it('should serve getTimelineCalendarEventsFromCompanyId identically to the object-record resolver', async () => { + const [fromObjectRecord, fromLegacy] = await Promise.all([ + requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + 'company', + personWithEvents.companyId, + ), + makeGraphqlAPIRequest({ + query: buildLegacyQuery( + 'getTimelineCalendarEventsFromCompanyId', + 'companyId', + CALENDAR_SELECTION, + ), + variables: { + id: personWithEvents.companyId, + page: 1, + pageSize: PAGE_SIZE, + }, + }), + ]); + + expect(fromLegacy.body.errors).toBeUndefined(); + expect(fromLegacy.body.data.getTimelineCalendarEventsFromCompanyId).toEqual( + fromObjectRecord.body.data.getTimelineCalendarEventsFromObjectRecord, + ); + }); + + describe('with a custom object that has no people on its timeline', () => { + const CUSTOM_OBJECT_NAME_SINGULAR = 'timelineProbe'; + const CUSTOM_RECORD_ID = '20202020-cccc-4000-8000-0000000000a1'; + + let customObjectMetadataId: string; + + beforeAll(async () => { + const { data } = await createOneObjectMetadata({ + input: { + nameSingular: CUSTOM_OBJECT_NAME_SINGULAR, + namePlural: 'timelineProbes', + labelSingular: 'Timeline Probe', + labelPlural: 'Timeline Probes', + icon: 'IconRadar', + }, + expectToFail: false, + }); + + customObjectMetadataId = data.createOneObject.id; + + await makeGraphqlAPIRequest( + createOneOperationFactory({ + objectMetadataSingularName: CUSTOM_OBJECT_NAME_SINGULAR, + gqlFields: 'id', + data: { id: CUSTOM_RECORD_ID, name: 'Probe' }, + }), + ); + }); + + afterAll(async () => { + await updateOneObjectMetadata({ + input: { + idToUpdate: customObjectMetadataId, + updatePayload: { isActive: false }, + }, + expectToFail: false, + }); + + await deleteOneObjectMetadata({ + input: { idToDelete: customObjectMetadataId }, + expectToFail: false, + }); + }); + + it('should return an empty message timeline instead of crashing', async () => { + const response = await requestTimeline( + GET_TIMELINE_THREADS, + CUSTOM_OBJECT_NAME_SINGULAR, + CUSTOM_RECORD_ID, + ); + + expect(response.body.errors).toBeUndefined(); + expect( + response.body.data.getTimelineThreadsFromObjectRecord + .totalNumberOfThreads, + ).toBe(0); + expect( + response.body.data.getTimelineThreadsFromObjectRecord.timelineThreads, + ).toEqual([]); + }); + + it('should return an empty calendar timeline instead of crashing', async () => { + const response = await requestTimeline( + GET_TIMELINE_CALENDAR_EVENTS, + CUSTOM_OBJECT_NAME_SINGULAR, + CUSTOM_RECORD_ID, + ); + + expect(response.body.errors).toBeUndefined(); + expect( + response.body.data.getTimelineCalendarEventsFromObjectRecord + .totalNumberOfCalendarEvents, + ).toBe(0); + expect( + response.body.data.getTimelineCalendarEventsFromObjectRecord + .timelineCalendarEvents, + ).toEqual([]); + }); + }); +});