Refactor global datasource part 3 (#16447)
## Context Following https://github.com/twentyhq/twenty/pull/16399 Now using the new global orm manager everywhere and returning a GlobalDatasource/WorkspaceDatasource based on a feature flag. This means we now need to wrap all our ORM calls within executeInWorkspaceContext callback (at least for now) so the global datasource can dynamically hydrate its context via the new store (the global datasource does not store anything related to workspaces as it is now a unique singleton). If feature flag is off it still uses local data stored in the workspace datasource.
This commit is contained in:
+7
-6
@@ -2,8 +2,8 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
|
||||
|
||||
@@ -36,18 +36,19 @@ describe('TimelineCalendarEventService', () => {
|
||||
findAndCount: jest.fn(),
|
||||
};
|
||||
|
||||
const mockTwentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest
|
||||
const mockGlobalWorkspaceOrmManager = {
|
||||
getRepository: jest.fn().mockResolvedValue(mockCalendarEventRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockCalendarEventRepository),
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TimelineCalendarEventService,
|
||||
{
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: mockTwentyORMGlobalManager,
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: mockGlobalWorkspaceOrmManager,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
+200
-181
@@ -6,7 +6,8 @@ import { Any } from 'typeorm';
|
||||
|
||||
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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
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 { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
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';
|
||||
@@ -15,10 +16,9 @@ import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/
|
||||
@Injectable()
|
||||
export class TimelineCalendarEventService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
) {}
|
||||
|
||||
// TODO: Align return type with the entities to avoid mapping
|
||||
async getCalendarEventsFromPersonIds({
|
||||
currentWorkspaceMemberId,
|
||||
personIds,
|
||||
@@ -32,136 +32,141 @@ export class TimelineCalendarEventService {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const offset = (page - 1) * pageSize;
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
const calendarEventRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarEventWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEvent',
|
||||
);
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const calendarEventIds = await calendarEventRepository.find({
|
||||
where: {
|
||||
calendarEventParticipants: {
|
||||
personId: Any(personIds),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
startsAt: true,
|
||||
},
|
||||
skip: offset,
|
||||
take: pageSize,
|
||||
order: {
|
||||
startsAt: 'DESC',
|
||||
},
|
||||
});
|
||||
const calendarEventRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarEventWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEvent',
|
||||
);
|
||||
|
||||
const ids = calendarEventIds.map(({ id }) => id);
|
||||
|
||||
if (ids.length <= 0) {
|
||||
return {
|
||||
totalNumberOfCalendarEvents: 0,
|
||||
timelineCalendarEvents: [],
|
||||
};
|
||||
}
|
||||
|
||||
// We've split the query into two parts, because we want to fetch all the participants without any filtering
|
||||
const [events, total] = await calendarEventRepository.findAndCount({
|
||||
where: {
|
||||
id: Any(ids),
|
||||
},
|
||||
relations: {
|
||||
calendarEventParticipants: {
|
||||
person: true,
|
||||
workspaceMember: true,
|
||||
},
|
||||
calendarChannelEventAssociations: {
|
||||
calendarChannel: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
const calendarEventIds = await calendarEventRepository.find({
|
||||
where: {
|
||||
calendarEventParticipants: {
|
||||
personId: Any(personIds),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
select: {
|
||||
id: true,
|
||||
startsAt: true,
|
||||
},
|
||||
skip: offset,
|
||||
take: pageSize,
|
||||
order: {
|
||||
startsAt: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
// Keep events in the same order as they ids were returned
|
||||
const orderedEvents = events.sort(
|
||||
(a, b) => ids.indexOf(a.id) - ids.indexOf(b.id),
|
||||
);
|
||||
const ids = calendarEventIds.map(({ id }) => id);
|
||||
|
||||
const timelineCalendarEvents = orderedEvents.map((event) => {
|
||||
const participants = event.calendarEventParticipants.map(
|
||||
(participant) => ({
|
||||
calendarEventId: event.id,
|
||||
personId: participant.personId ?? null,
|
||||
workspaceMemberId: participant.workspaceMemberId ?? null,
|
||||
firstName:
|
||||
participant.person?.name?.firstName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
'',
|
||||
lastName:
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
displayName:
|
||||
participant.person?.name?.firstName ||
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
participant.displayName ||
|
||||
participant.handle ||
|
||||
'',
|
||||
avatarUrl:
|
||||
participant.person?.avatarUrl ||
|
||||
participant.workspaceMember?.avatarUrl ||
|
||||
'',
|
||||
handle: participant.handle ?? '',
|
||||
}),
|
||||
);
|
||||
if (ids.length <= 0) {
|
||||
return {
|
||||
totalNumberOfCalendarEvents: 0,
|
||||
timelineCalendarEvents: [],
|
||||
};
|
||||
}
|
||||
|
||||
const isCalendarEventImportedByCurrentWorkspaceMember =
|
||||
event.calendarChannelEventAssociations.some(
|
||||
(association) =>
|
||||
association.calendarChannel.connectedAccount.accountOwnerId ===
|
||||
currentWorkspaceMemberId,
|
||||
const [events, total] = await calendarEventRepository.findAndCount({
|
||||
where: {
|
||||
id: Any(ids),
|
||||
},
|
||||
relations: {
|
||||
calendarEventParticipants: {
|
||||
person: true,
|
||||
workspaceMember: true,
|
||||
},
|
||||
calendarChannelEventAssociations: {
|
||||
calendarChannel: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const orderedEvents = events.sort(
|
||||
(a, b) => ids.indexOf(a.id) - ids.indexOf(b.id),
|
||||
);
|
||||
|
||||
const visibility =
|
||||
event.calendarChannelEventAssociations.some(
|
||||
(association) =>
|
||||
association.calendarChannel.visibility === 'SHARE_EVERYTHING',
|
||||
) || isCalendarEventImportedByCurrentWorkspaceMember
|
||||
? CalendarChannelVisibility.SHARE_EVERYTHING
|
||||
: CalendarChannelVisibility.METADATA;
|
||||
const timelineCalendarEvents = orderedEvents.map((event) => {
|
||||
const participants = event.calendarEventParticipants.map(
|
||||
(participant) => ({
|
||||
calendarEventId: event.id,
|
||||
personId: participant.personId ?? null,
|
||||
workspaceMemberId: participant.workspaceMemberId ?? null,
|
||||
firstName:
|
||||
participant.person?.name?.firstName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
'',
|
||||
lastName:
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
displayName:
|
||||
participant.person?.name?.firstName ||
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
participant.displayName ||
|
||||
participant.handle ||
|
||||
'',
|
||||
avatarUrl:
|
||||
participant.person?.avatarUrl ||
|
||||
participant.workspaceMember?.avatarUrl ||
|
||||
'',
|
||||
handle: participant.handle ?? '',
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...omit(event, [
|
||||
'calendarEventParticipants',
|
||||
'calendarChannelEventAssociations',
|
||||
]),
|
||||
title:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: (event.title ?? ''),
|
||||
description:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: (event.description ?? ''),
|
||||
startsAt: event.startsAt as unknown as Date,
|
||||
endsAt: event.endsAt as unknown as Date,
|
||||
participants,
|
||||
visibility,
|
||||
location: event.location ?? '',
|
||||
conferenceSolution: event.conferenceSolution ?? '',
|
||||
};
|
||||
});
|
||||
const isCalendarEventImportedByCurrentWorkspaceMember =
|
||||
event.calendarChannelEventAssociations.some(
|
||||
(association) =>
|
||||
association.calendarChannel.connectedAccount.accountOwnerId ===
|
||||
currentWorkspaceMemberId,
|
||||
);
|
||||
|
||||
return {
|
||||
totalNumberOfCalendarEvents: total,
|
||||
timelineCalendarEvents,
|
||||
};
|
||||
const visibility =
|
||||
event.calendarChannelEventAssociations.some(
|
||||
(association) =>
|
||||
association.calendarChannel.visibility === 'SHARE_EVERYTHING',
|
||||
) || isCalendarEventImportedByCurrentWorkspaceMember
|
||||
? CalendarChannelVisibility.SHARE_EVERYTHING
|
||||
: CalendarChannelVisibility.METADATA;
|
||||
|
||||
return {
|
||||
...omit(event, [
|
||||
'calendarEventParticipants',
|
||||
'calendarChannelEventAssociations',
|
||||
]),
|
||||
title:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: (event.title ?? ''),
|
||||
description:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: (event.description ?? ''),
|
||||
startsAt: event.startsAt as unknown as Date,
|
||||
endsAt: event.endsAt as unknown as Date,
|
||||
participants,
|
||||
visibility,
|
||||
location: event.location ?? '',
|
||||
conferenceSolution: event.conferenceSolution ?? '',
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
totalNumberOfCalendarEvents: total,
|
||||
timelineCalendarEvents,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async getCalendarEventsFromCompanyId({
|
||||
@@ -177,39 +182,46 @@ export class TimelineCalendarEventService {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const personRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<PersonWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'person',
|
||||
);
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
const personIds = await personRepository.find({
|
||||
where: {
|
||||
companyId,
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const personRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<PersonWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'person',
|
||||
);
|
||||
|
||||
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;
|
||||
},
|
||||
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;
|
||||
);
|
||||
}
|
||||
|
||||
async getCalendarEventsFromOpportunityId({
|
||||
@@ -225,36 +237,43 @@ export class TimelineCalendarEventService {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const opportunityRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<OpportunityWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'opportunity',
|
||||
);
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
const opportunity = await opportunityRepository.findOne({
|
||||
where: {
|
||||
id: opportunityId,
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const opportunityRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<OpportunityWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'opportunity',
|
||||
);
|
||||
|
||||
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;
|
||||
},
|
||||
select: {
|
||||
companyId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!opportunity?.companyId) {
|
||||
return {
|
||||
totalNumberOfCalendarEvents: 0,
|
||||
timelineCalendarEvents: [],
|
||||
};
|
||||
}
|
||||
|
||||
const calendarEvents = await this.getCalendarEventsFromCompanyId({
|
||||
currentWorkspaceMemberId,
|
||||
companyId: opportunity.companyId,
|
||||
workspaceId,
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
return calendarEvents;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user