perf: Replace un-needed context fetching (#10657)

* Replace ctx.user.credentials to fetch in their own usecases

* Remove rawavatar from return

* Remove avatar rawAvatar from handler

* Fix fallback avatars

* fix: profile.slug already includes /team

* perf: Deprecate useAvatarQuery hook

* Extract to reusable credential func

* Fix type errors for credentials

* credentialOwner was inferred incorrectly, string non-nullable

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
sean-brydon 2023-08-10 18:07:09 +01:00 committed by GitHub
parent a153f9627d
commit 57384eb921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 84 additions and 62 deletions

View File

@ -716,10 +716,7 @@ const EventTypeListHeading = ({
<Avatar
alt={profile?.name || ""}
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
imageSrc={
`${orgBranding?.fullDomain ?? WEBAPP_URL}/${teamId ? "team/" : ""}${profile.slug}/avatar.png` ||
undefined
}
imageSrc={`${orgBranding?.fullDomain ?? WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size="md"
className="mt-1 inline-flex justify-center"
/>

View File

@ -77,10 +77,9 @@ type FormValues = {
const ProfileView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: session, update } = useSession();
const { data: _session, update } = useSession();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: avatar, isLoading: isLoadingAvatar } = trpc.viewer.avatar.useQuery();
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (res) => {
showToast(t("settings_updated_successfully"), "success");
@ -205,14 +204,14 @@ const ProfileView = () => {
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
if (isLoading || !user || isLoadingAvatar || !avatar)
if (isLoading || !user)
return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
);
const defaultValues = {
username: user.username || "",
avatar: avatar.avatar || "",
avatar: user.avatar || "",
name: user.name || "",
email: user.email || "",
bio: user.bio || "",

View File

@ -12,7 +12,6 @@ import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { IdentityProvider, MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
import type { VerticalTabItemProps } from "@calcom/ui";
import { Badge, Button, ErrorBoundary, Skeleton, useMeta, VerticalTabItem } from "@calcom/ui";
import {
@ -137,7 +136,6 @@ const organizationRequiredKeys = ["organization"];
const useTabs = () => {
const session = useSession();
const { data: user } = trpc.viewer.me.useQuery();
const { data: avatar } = useAvatarQuery();
const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN;
@ -145,7 +143,7 @@ const useTabs = () => {
if (tab.href === "/settings/my-account") {
tab.name = user?.name || "my_account";
tab.icon = undefined;
tab.avatar = avatar?.avatar || WEBAPP_URL + "/" + session?.data?.user?.username + "/avatar.png";
tab.avatar = WEBAPP_URL + "/" + session?.data?.user?.username + "/avatar.png";
} else if (
tab.href === "/settings/security" &&
user?.identityProvider === IdentityProvider.GOOGLE &&

View File

@ -29,7 +29,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { isKeyInObject } from "@calcom/lib/isKeyInObject";
import type { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { SVGComponent } from "@calcom/types/SVGComponent";
@ -308,7 +307,6 @@ interface UserDropdownProps {
function UserDropdown({ small }: UserDropdownProps) {
const { t } = useLocale();
const { data: user } = useMeQuery();
const { data: avatar } = useAvatarQuery();
const utils = trpc.useContext();
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -373,7 +371,7 @@ function UserDropdown({ small }: UserDropdownProps) {
)}>
<Avatar
size={small ? "xs" : "xsm"}
imageSrc={avatar?.avatar || WEBAPP_URL + "/" + user.username + "/avatar.png"}
imageSrc={WEBAPP_URL + "/" + user.username + "/avatar.png"}
alt={user.username || "Nameless User"}
className="overflow-hidden"
/>

View File

@ -0,0 +1,22 @@
import { prisma } from "@calcom/prisma";
export async function getUsersCredentials(userId: number) {
const credentials = await prisma.credential.findMany({
where: {
userId,
},
select: {
id: true,
type: true,
key: true,
userId: true,
appId: true,
invalid: true,
teamId: true,
},
orderBy: {
id: "asc",
},
});
return credentials;
}

View File

@ -1,13 +0,0 @@
import { trpc } from "../trpc";
export function useAvatarQuery() {
const avatarQuery = trpc.viewer.avatar.useQuery(undefined, {
retry(failureCount) {
return failureCount > 3;
},
});
return avatarQuery;
}
export default useAvatarQuery;

View File

@ -1,7 +1,6 @@
import type { Session } from "next-auth";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { defaultAvatarSrc } from "@calcom/lib/defaultAvatarImage";
import { MembershipRole } from "@calcom/prisma/enums";
import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
@ -37,27 +36,12 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
theme: true,
createdDate: true,
hideBranding: true,
avatar: true,
twoFactorEnabled: true,
disableImpersonation: true,
identityProvider: true,
brandColor: true,
darkBrandColor: true,
away: true,
credentials: {
select: {
id: true,
type: true,
key: true,
userId: true,
appId: true,
invalid: true,
teamId: true,
},
orderBy: {
id: "asc",
},
},
selectedCalendars: {
select: {
externalId: true,
@ -102,11 +86,8 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
const userMetaData = userMetadata.parse(user.metadata || {});
const orgMetadata = teamMetadataSchema.parse(user.organization?.metadata || {});
const rawAvatar = user.avatar;
// This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url
user.avatar = rawAvatar
? `${WEBAPP_URL}/${user.username}/avatar.png?orgId=${user.organizationId}`
: defaultAvatarSrc({ email });
const locale = user?.locale || ctx.locale;
const isOrgAdmin = !!user.organization?.members.length;
@ -116,13 +97,14 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
}
return {
...user,
avatar:
`${WEBAPP_URL}/${user.username}/avatar.png?` + user.organizationId && `orgId=${user.organizationId}`,
organization: {
...user.organization,
isOrgAdmin,
metadata: orgMetadata,
},
id,
rawAvatar,
email,
username,
locale,

View File

@ -1,4 +1,5 @@
import getApps from "@calcom/app-store/utils";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { TRPCError } from "@trpc/server";
@ -15,7 +16,7 @@ type AppByIdOptions = {
export const appByIdHandler = async ({ ctx, input }: AppByIdOptions) => {
const { user } = ctx;
const appId = input.appId;
const { credentials } = user;
const credentials = await getUsersCredentials(user.id);
const apps = getApps(credentials);
const appFromDb = apps.find((app) => app.slug === appId);
if (!appFromDb) {

View File

@ -8,6 +8,6 @@ type AvatarOptions = {
export const avatarHandler = async ({ ctx }: AvatarOptions) => {
return {
avatar: ctx.user.rawAvatar,
avatar: ctx.user.avatar,
};
};

View File

@ -1,5 +1,7 @@
import type { CredentialOwner } from "@calcom/app-store/types";
import getEnabledApps from "@calcom/lib/apps/getEnabledApps";
import getInstallCountPerApp from "@calcom/lib/apps/getInstallCountPerApp";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -43,7 +45,7 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
teamId,
sortByMostPopular,
} = input;
let { credentials } = user;
let credentials = await getUsersCredentials(user.id);
let userTeams: TeamQuery[] = [];
if (includeTeamInstalledApps || teamId) {
@ -138,9 +140,17 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
team.members[0].role === MembershipRole.ADMIN || team.members[0].role === MembershipRole.OWNER,
};
});
// type infer as CredentialOwner
const credentialOwner: CredentialOwner = {
name: user.name,
avatar: user.avatar,
};
return {
...app,
...(teams.length && { credentialOwner: { name: user.name, avatar: user.avatar } }),
...(teams.length && {
credentialOwner,
}),
userCredentialIds,
invalidCredentialIds,
teams,

View File

@ -1,3 +1,4 @@
import { WEBAPP_URL } from "@calcom/lib/constants";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
type MeOptions = {
@ -23,7 +24,7 @@ export const meHandler = async ({ ctx }: MeOptions) => {
locale: user.locale,
timeFormat: user.timeFormat,
timeZone: user.timeZone,
avatar: user.avatar,
avatar: `${WEBAPP_URL}/${user.username}/avatar.png`,
createdDate: user.createdDate,
trialEndsAt: user.trialEndsAt,
defaultScheduleId: user.defaultScheduleId,

View File

@ -1,4 +1,5 @@
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -16,7 +17,8 @@ type SetDestinationCalendarOptions = {
export const setDestinationCalendarHandler = async ({ ctx, input }: SetDestinationCalendarOptions) => {
const { user } = ctx;
const { integration, externalId, eventTypeId } = input;
const calendarCredentials = getCalendarCredentials(user.credentials);
const credentials = await getUsersCredentials(user.id);
const calendarCredentials = getCalendarCredentials(credentials);
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
const allCals = connectedCalendars.map((cal) => cal.calendars ?? []).flat();

View File

@ -1,6 +1,7 @@
import z from "zod";
import getApps from "@calcom/app-store/utils";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { prisma } from "@calcom/prisma";
import { userMetadata } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -21,7 +22,7 @@ export const updateUserDefaultConferencingAppHandler = async ({
input,
}: UpdateUserDefaultConferencingAppOptions) => {
const currentMetadata = userMetadata.parse(ctx.user.metadata);
const credentials = ctx.user.credentials;
const credentials = await getUsersCredentials(ctx.user.id);
const foundApp = getApps(credentials, true).filter((app) => app.slug === input.appSlug)[0];
const appLocation = foundApp?.appData?.location;

View File

@ -9,6 +9,7 @@ import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
import { getTranslation } from "@calcom/lib/server";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { BookingStatus, MembershipRole, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums";
@ -212,7 +213,19 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
}
if (confirmed) {
await handleConfirmation({ user, evt, recurringEventId, prisma, bookingId, booking });
const credentials = await getUsersCredentials(user.id);
const userWithCredentials = {
...user,
credentials,
};
await handleConfirmation({
user: userWithCredentials,
evt,
recurringEventId,
prisma,
bookingId,
booking,
});
} else {
evt.rejectionReason = rejectionReason;
if (recurringEventId) {

View File

@ -4,6 +4,7 @@ import { sendLocationChangeEmails } from "@calcom/emails";
import { parseRecurringEvent } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { prisma } from "@calcom/prisma";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
@ -86,10 +87,12 @@ export const editLocationHandler = async ({ ctx, input }: EditLocationOptions) =
seatsShowAttendees: booking.eventType?.seatsShowAttendees,
};
const credentials = await getUsersCredentials(ctx.user.id);
const eventManager = new EventManager({
...ctx.user,
credentials: [
...(ctx.user.credentials ? ctx.user.credentials : []),
...(credentials ? credentials : []),
...(conferenceCredential ? [conferenceCredential] : []),
],
});

View File

@ -17,6 +17,7 @@ import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
import { getTranslation } from "@calcom/lib/server";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { prisma } from "@calcom/prisma";
import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { BookingStatus, WorkflowMethods } from "@calcom/prisma/enums";
@ -189,8 +190,9 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
// Handling calendar and videos cancellation
// This can set previous time as available, until virtual calendar is done
const credentials = await getUsersCredentials(user.id);
const credentialsMap = new Map();
user.credentials.forEach((credential) => {
credentials.forEach((credential) => {
credentialsMap.set(credential.type, credential);
});
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter((ref) =>

View File

@ -4,6 +4,7 @@ import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
import { DailyLocationType } from "@calcom/app-store/locations";
import getApps from "@calcom/app-store/utils";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import type { PrismaClient } from "@calcom/prisma/client";
import { SchedulingType } from "@calcom/prisma/enums";
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
@ -41,7 +42,7 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
}
if (defaultConferencingData && defaultConferencingData.appSlug !== "daily-video") {
const credentials = ctx.user.credentials;
const credentials = await getUsersCredentials(ctx.user.id);
const foundApp = getApps(credentials, true).filter(
(app) => app.slug === defaultConferencingData.appSlug
)[0]; // There is only one possible install here so index [0] is the one we are looking for ;

View File

@ -52,9 +52,14 @@ export function Avatar(props: AvatarProps) {
/>
<AvatarPrimitive.Fallback delayMs={600} asChild={props.asChild} className="flex items-center">
<>
{props.fallback && !gravatarFallbackMd5 && props.fallback}
{gravatarFallbackMd5 && (
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={rootClass} />
{props.fallback ? (
props.fallback
) : (
<img
src={defaultAvatarSrc({ md5: gravatarFallbackMd5 ?? "fallback" })}
alt={alt}
className={rootClass}
/>
)}
</>
</AvatarPrimitive.Fallback>