perf: Move /me stats to separate tRPC handler (#17206)

* perf: Move /me stats to separate tRPC handler

* Update packages/trpc/server/routers/loggedInViewer/meStats.handler.ts

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* Rename and skipping batch for myStats

* Refactor of counting team events

* Fixed type issues

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Keith Williams
2024-11-13 20:55:57 -03:00
committed by GitHub
co-authored by Udit Takkar
parent c197c33fd0
commit 7dbcb62724
7 changed files with 110 additions and 54 deletions
@@ -28,11 +28,18 @@ const usePostHogHook: () => PostHogHook = isPostHogEnabled
const usePostHog = () => {
const posthog = usePostHogHook();
const { data } = trpc.viewer.me.useQuery();
const { data: statsData } = trpc.viewer.myStats.useQuery(undefined, {
trpc: {
context: {
skipBatch: true,
},
},
});
const { hasPaidPlan } = useHasPaidPlan();
const { hasTeamPlan } = useHasTeamPlan();
const identify = async () => {
if (!data) return;
if (!data || !statsData) return;
posthog.identify(String(data.id), {
distinctId: String(data.id),
@@ -49,13 +56,13 @@ const usePostHog = () => {
has_team_plan: hasTeamPlan,
metadata: data?.metadata,
completed_onboarding: data?.completedOnboarding,
sum_of_bookings: data?.sumOfBookings,
sum_of_calendars: data?.sumOfCalendars,
sum_of_teams: data?.sumOfTeams,
sum_of_bookings: statsData?.sumOfBookings,
sum_of_calendars: statsData?.sumOfCalendars,
sum_of_teams: statsData?.sumOfTeams,
has_orgs_plan: !!data?.organizationId,
organization: data?.organization?.slug,
sum_of_event_types: data?.sumOfEventTypes,
sum_of_team_event_types: data?.sumOfTeamEventTypes,
sum_of_event_types: statsData?.sumOfEventTypes,
sum_of_team_event_types: statsData?.sumOfTeamEventTypes,
is_premium: data?.isPremium,
});
};
@@ -26,11 +26,18 @@ const useIntercomHook = isInterComEnabled
export const useIntercom = () => {
const hookData = useIntercomHook();
const { data } = trpc.viewer.me.useQuery();
const { data: statsData } = trpc.viewer.myStats.useQuery(undefined, {
trpc: {
context: {
skipBatch: true,
},
},
});
const { hasPaidPlan } = useHasPaidPlan();
const { hasTeamPlan } = useHasTeamPlan();
const boot = async () => {
if (!data) return;
if (!data || !statsData) return;
let userHash;
const req = await fetch(`/api/intercom-hash`);
@@ -61,13 +68,13 @@ export const useIntercom = () => {
metadata: data?.metadata,
completed_onboarding: data.completedOnboarding,
is_logged_in: !!data,
sum_of_bookings: data?.sumOfBookings,
sum_of_calendars: data?.sumOfCalendars,
sum_of_teams: data?.sumOfTeams,
sum_of_bookings: statsData?.sumOfBookings,
sum_of_calendars: statsData?.sumOfCalendars,
sum_of_teams: statsData?.sumOfTeams,
has_orgs_plan: !!data?.organizationId,
organization: data?.organization?.slug,
sum_of_event_types: data?.sumOfEventTypes,
sum_of_team_event_types: data?.sumOfTeamEventTypes,
sum_of_event_types: statsData?.sumOfEventTypes,
sum_of_team_event_types: statsData?.sumOfTeamEventTypes,
is_premium: data?.isPremium,
},
});
@@ -104,13 +111,13 @@ export const useIntercom = () => {
metadata: data?.metadata,
completed_onboarding: data?.completedOnboarding,
is_logged_in: !!data,
sum_of_bookings: data?.sumOfBookings,
sum_of_calendars: data?.sumOfCalendars,
sum_of_teams: data?.sumOfTeams,
sum_of_bookings: statsData?.sumOfBookings,
sum_of_calendars: statsData?.sumOfCalendars,
sum_of_teams: statsData?.sumOfTeams,
has_orgs_plan: !!data?.organizationId,
organization: data?.organization?.slug,
sum_of_event_types: data?.sumOfEventTypes,
sum_of_team_event_types: data?.sumOfTeamEventTypes,
sum_of_event_types: statsData?.sumOfEventTypes,
sum_of_team_event_types: statsData?.sumOfTeamEventTypes,
is_premium: data?.isPremium,
},
});
@@ -122,13 +129,22 @@ export const useIntercom = () => {
export const useBootIntercom = () => {
const { boot } = useIntercom();
const { data: user } = trpc.viewer.me.useQuery();
const { data: statsData } = trpc.viewer.myStats.useQuery(undefined, {
trpc: {
context: {
skipBatch: true,
},
},
});
useEffect(() => {
// not using useMediaQuery as it toggles between true and false
const showIntercom = localStorage.getItem("showIntercom");
if (!isInterComEnabled || showIntercom === "false" || window.innerWidth <= 768 || !user) return;
if (!isInterComEnabled || showIntercom === "false" || window.innerWidth <= 768 || !user || !statsData)
return;
boot();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);
}, [user, statsData]);
};
export default useIntercom;
@@ -16,6 +16,7 @@ import { ZLocationOptionsInputSchema } from "./locationOptions.schema";
import { ZNoShowInputSchema } from "./markNoShow.schema";
import { ZOutOfOfficeInputSchema, ZOutOfOfficeDelete } from "./outOfOffice.schema";
import { me } from "./procedures/me";
import { myStats } from "./procedures/myStats";
import { platformMe } from "./procedures/platformMe";
import { teamsAndUserProfilesQuery } from "./procedures/teamsAndUserProfilesQuery";
import { ZRemoveNotificationsSubscriptionInputSchema } from "./removeNotificationsSubscription.schema";
@@ -32,6 +33,7 @@ const namespaced = (s: string) => `${NAMESPACE}.${s}`;
type AppsRouterHandlerCache = {
me?: typeof import("./me.handler").meHandler;
myStats?: typeof import("./myStats.handler").myStatsHandler;
platformMe?: typeof import("./platformMe.handler").platformMeHandler;
shouldVerifyEmail?: typeof import("./shouldVerifyEmail.handler").shouldVerifyEmailHandler;
deleteMe?: typeof import("./deleteMe.handler").deleteMeHandler;
@@ -72,6 +74,7 @@ const UNSTABLE_HANDLER_CACHE: AppsRouterHandlerCache = {};
export const loggedInViewerRouter = router({
me,
myStats,
platformMe,
deleteMe: authedProcedure.input(ZDeleteMeInputSchema).mutation(async ({ ctx, input }) => {
@@ -72,36 +72,6 @@ export const meHandler = async ({ ctx, input }: MeOptions) => {
identityProviderEmail = account?.providerEmail || "";
}
const additionalUserInfo = await prisma.user.findFirst({
where: {
id: user.id,
},
select: {
bookings: {
select: { id: true },
},
selectedCalendars: true,
teams: {
select: {
team: {
select: {
id: true,
eventTypes: true,
},
},
},
},
eventTypes: {
select: { id: true },
},
},
});
let sumOfTeamEventTypes = 0;
for (const team of additionalUserInfo?.teams || []) {
for (const _eventType of team.team.eventTypes) {
sumOfTeamEventTypes++;
}
}
const userMetadataPrased = userMetadata.parse(user.metadata);
// Destructuring here only makes it more illegible
@@ -159,12 +129,7 @@ export const meHandler = async ({ ctx, input }: MeOptions) => {
receiveMonthlyDigestEmail: user.receiveMonthlyDigestEmail,
...profileData,
secondaryEmails,
sumOfBookings: additionalUserInfo?.bookings.length,
sumOfCalendars: additionalUserInfo?.selectedCalendars.length,
sumOfTeams: additionalUserInfo?.teams.length,
sumOfEventTypes: additionalUserInfo?.eventTypes.length,
isPremium: userMetadataPrased?.isPremium,
sumOfTeamEventTypes,
...(passwordAdded ? { passwordAdded } : {}),
};
};
@@ -0,0 +1,57 @@
import type { Session } from "next-auth";
import prisma from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
type MyStatsOptions = {
ctx: {
user: NonNullable<TrpcSessionUser>;
session: Session;
};
};
export const myStatsHandler = async ({ ctx }: MyStatsOptions) => {
const { user: sessionUser } = ctx;
const additionalUserInfo = await prisma.user.findFirst({
where: {
id: sessionUser.id,
},
select: {
_count: {
select: {
bookings: true,
selectedCalendars: true,
teams: true,
eventTypes: true,
},
},
teams: {
select: {
team: {
select: {
eventTypes: {
select: {
id: true,
},
},
},
},
},
},
},
});
const sumOfTeamEventTypes = additionalUserInfo?.teams.reduce(
(sum, team) => sum + team.team.eventTypes.length,
0
);
return {
id: sessionUser.id,
sumOfBookings: additionalUserInfo?._count.bookings,
sumOfCalendars: additionalUserInfo?._count.selectedCalendars,
sumOfTeams: additionalUserInfo?._count.teams,
sumOfEventTypes: additionalUserInfo?._count.eventTypes,
sumOfTeamEventTypes,
};
};
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,7 @@
import authedProcedure from "../../../procedures/authedProcedure";
export const myStats = authedProcedure.query(async ({ ctx, input }) => {
const handler = (await import("../myStats.handler")).myStatsHandler;
return handler({ ctx });
});