fix: fetch next batch if event types are empty and cursor exists (#17109)

* fix: fetch next batch if event types are empty and cursor exists

* refactor: improve

* fix: add isFetchingForFirstTime

* chore: add number type

* fix: type err

* chore: add logs

* fix: type err

* fix: conflict

* fix: bug

* fix: type err

* chore: try changing type

* chore: add nul

* Update packages/trpc/server/routers/viewer/eventTypes/getEventTypesFromGroup.handler.ts

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* chore: use log.info

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
This commit is contained in:
Udit Takkar
2024-11-13 14:37:03 +00:00
committed by GitHub
co-authored by sean-brydon Bailey Pumfleet
parent 18c7c3c940
commit 5c6e60b726
2 changed files with 80 additions and 22 deletions
@@ -81,7 +81,7 @@ interface InfiniteEventTypeListProps {
group: InfiniteEventTypeGroup;
readOnly: boolean;
bookerUrl: string | null;
pages: { nextCursor: number | undefined; eventTypes: InfiniteEventType[] }[] | undefined;
pages: { nextCursor: number | null | undefined; eventTypes: InfiniteEventType[] }[] | undefined;
lockedByOrg?: boolean;
isPending?: boolean;
debouncedSearchTerm?: string;
@@ -349,9 +349,14 @@ export const InfiniteEventTypeList = ({
group: { teamId: group?.teamId, parentId: group?.parentId },
},
(data) => {
if (!data) return { pages: [], pageParams: [] };
return {
pageParams: data?.pageParams ?? [],
pages: newOrder,
...data,
pages: newOrder.map((page) => ({
...page,
nextCursor: page.nextCursor ?? undefined,
})),
};
}
);
@@ -6,7 +6,6 @@ import { EventTypeRepository } from "@calcom/lib/server/repository/eventType";
import { prisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";
// import { SchedulingType } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "../../../trpc";
import type { TGetEventTypesFromGroupSchema } from "./getByViewer.schema";
import { mapEventType } from "./util";
@@ -22,8 +21,15 @@ type GetByViewerOptions = {
};
type EventType = Awaited<ReturnType<typeof EventTypeRepository.findAllByUpId>>[number];
type MappedEventType = Awaited<ReturnType<typeof mapEventType>>;
export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions) => {
export const getEventTypesFromGroup = async ({
ctx,
input,
}: GetByViewerOptions): Promise<{
eventTypes: MappedEventType[];
nextCursor: number | null | undefined;
}> => {
await checkRateLimitAndThrowError({
identifier: `eventTypes:getEventTypesFromGroup:${ctx.user.id}`,
rateLimitingType: "common",
@@ -31,7 +37,7 @@ export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions)
const userProfile = ctx.user.profile;
const { group, limit, cursor, filters, searchQuery } = input;
const { teamId, parentId } = group;
const { teamId } = group;
const isFilterSet = (filters && hasFilter(filters)) || !!teamId;
const isUpIdInFilter = filters?.upIds?.includes(userProfile.upId);
@@ -39,6 +45,41 @@ export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions)
const shouldListUserEvents =
!isFilterSet || isUpIdInFilter || (isFilterSet && filters?.upIds && !isUpIdInFilter);
const eventTypes: MappedEventType[] = [];
const currentCursor = cursor;
let nextCursor: number | null | undefined = undefined;
let isFetchingForFirstTime = true;
const fetchAndFilterEventTypes = async () => {
const batch = await fetchEventTypesBatch(ctx, input, shouldListUserEvents, currentCursor, searchQuery);
const filteredBatch = await filterEventTypes(batch.eventTypes, ctx.user.id, shouldListUserEvents, teamId);
eventTypes.push(...filteredBatch);
nextCursor = batch.nextCursor;
};
while (eventTypes.length < limit && (nextCursor || isFetchingForFirstTime)) {
await fetchAndFilterEventTypes();
isFetchingForFirstTime = false;
}
return {
eventTypes,
nextCursor: nextCursor ?? undefined,
};
};
const fetchEventTypesBatch = async (
ctx: GetByViewerOptions["ctx"],
input: GetByViewerOptions["input"],
shouldListUserEvents: boolean | undefined,
cursor: TGetEventTypesFromGroupSchema["cursor"],
searchQuery: TGetEventTypesFromGroupSchema["searchQuery"]
) => {
const userProfile = ctx.user.profile;
const { group, limit, filters } = input;
const { teamId, parentId } = group;
const isFilterSet = (filters && hasFilter(filters)) || !!teamId;
const eventTypes: EventType[] = [];
if (shouldListUserEvents || !teamId) {
@@ -99,8 +140,8 @@ export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions)
eventTypes.push(...teamEventTypes);
}
let nextCursor: typeof cursor | undefined = undefined;
if (eventTypes && eventTypes.length > limit) {
let nextCursor: number | null | undefined = undefined;
if (eventTypes.length > limit) {
const nextItem = eventTypes.pop();
nextCursor = nextItem?.id;
}
@@ -108,37 +149,45 @@ export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions)
const mappedEventTypes = await Promise.all(eventTypes.map(mapEventType));
log.info(
"mappedEventTypes before filtering",
"fetchEventTypesBatch",
safeStringify({
input,
mappedEventTypes,
})
);
const filteredEventTypes = mappedEventTypes.filter((eventType) => {
const isAChildEvent = eventType.parentId;
if (!isAChildEvent) {
return { eventTypes: mappedEventTypes, nextCursor: nextCursor ?? undefined };
};
const filterEventTypes = async (
eventTypes: MappedEventType[],
userId: number,
shouldListUserEvents: boolean | undefined,
teamId: number | null | undefined
) => {
const filteredEventTypes = eventTypes.filter((eventType) => {
if (!eventType.parentId) {
return true;
}
// A child event only has one user
const childEventAssignee = eventType.users[0];
if (!childEventAssignee || childEventAssignee.id != ctx.user.id) {
if (!childEventAssignee || childEventAssignee.id !== userId) {
return false;
}
return true;
});
log.info(
"mappedEventTypes after filtering",
"mappedEventTypes before and after filtering",
safeStringify({
input,
filteredEventTypes,
beforeFiltering: eventTypes,
afterFiltering: filteredEventTypes,
})
);
const membership = await prisma.membership.findFirst({
where: {
userId: ctx.user.id,
userId,
teamId: teamId ?? 0,
accepted: true,
role: "MEMBER",
@@ -158,8 +207,12 @@ export const getEventTypesFromGroup = async ({ ctx, input }: GetByViewerOptions)
evType.hosts = [];
});
return {
eventTypes: filteredEventTypes || [],
nextCursor,
};
log.info(
"filteredEventTypes",
safeStringify({
filteredEventTypes,
})
);
return filteredEventTypes;
};