chore: reduce team data footprint (#11478)

pull/11484/head
Leo Giovanetti 2023-09-21 18:08:34 -03:00 committed by GitHub
parent 86b3a33ff6
commit 578495de87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 75 additions and 53 deletions

View File

@ -8,6 +8,7 @@ import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
@ -65,9 +66,9 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
// slug is a route parameter, we don't want to forward it to the next route
const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = routerQuery;
const EventTypes = () => (
const EventTypes = ({ eventTypes }: { eventTypes: NonNullable<(typeof team)["eventTypes"]> }) => (
<ul className="border-subtle rounded-md border">
{team.eventTypes.map((type, index) => (
{eventTypes.map((type, index) => (
<li
key={index}
className={classNames(
@ -160,8 +161,14 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted p-8 text-center">
<h2 className="font-cal text-emphasis mb-2 text-3xl">{" " + t("org_no_teams_yet")}</h2>
<p className="text-emphasis mx-auto max-w-md">{t("org_no_teams_yet_description")}</p>
<h2 className="font-cal text-emphasis mb-2 text-3xl">
{" " + t("org_no_teams_yet", { defaultValue: "This organization has no teams yet" })}
</h2>
<p className="text-emphasis mx-auto max-w-md">
{t("org_no_teams_yet_description", {
defaultValue: "If you are an administrator, be sure to create teams to be shown here.",
})}
</p>
</div>
</div>
</div>
@ -174,7 +181,10 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
description={teamName}
meeting={{
title: markdownStrippedBio,
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
profile: {
name: `${team.name}`,
image: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`,
},
}}
/>
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
@ -182,7 +192,9 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
<div className="relative">
<Avatar
alt={teamName}
imageSrc={getPlaceholderAvatar(team.parent ? team.parent.logo : team.logo, team.name)}
imageSrc={`${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${
team.slug
}/avatar.png`}
size="lg"
/>
</div>
@ -203,7 +215,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
<SubTeams />
) : (
<>
{(showMembers.isOn || !team.eventTypes.length) &&
{(showMembers.isOn || !team.eventTypes?.length) &&
(team.isPrivate ? (
<div className="w-full text-center">
<h2 className="text-emphasis font-semibold">{t("you_cannot_see_team_members")}</h2>
@ -211,9 +223,9 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
) : (
<Team team={team} />
))}
{!showMembers.isOn && team.eventTypes.length > 0 && (
{!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
<div className="mx-auto max-w-3xl ">
<EventTypes />
<EventTypes eventTypes={team.eventTypes} />
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */}
{!team.hideBookATeamMember && !team.isPrivate && (
@ -263,7 +275,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
context.params?.orgSlug
);
const flags = await getFeatureFlagMap(prisma);
const team = await getTeamWithMembers({ slug, orgSlug: currentOrgDomain });
const team = await getTeamWithMembers({
slug,
orgSlug: currentOrgDomain,
isTeamView: true,
isOrgView: isValidOrgDomain && context.resolvedUrl === "/",
});
const ssr = await ssrInit(context, {
noI18nPreload: isValidOrgDomain && !team?.parent,
});
@ -308,14 +325,15 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
} as const;
}
team.eventTypes = team.eventTypes.map((type) => ({
...type,
users: type.users.map((user) => ({
...user,
avatar: "/" + user.username + "/avatar.png",
})),
descriptionAsSafeHTML: markdownToSafeHTML(type.description),
}));
team.eventTypes =
team.eventTypes?.map((type) => ({
...type,
users: type.users.map((user) => ({
...user,
avatar: "/" + user.username + "/avatar.png",
})),
descriptionAsSafeHTML: markdownToSafeHTML(type.description),
})) ?? null;
const safeBio = markdownToSafeHTML(team.bio) || "";

View File

@ -10,10 +10,15 @@ import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
*/
export type OrganizationBranding =
| ({
/** 1 */
id: number;
/** Acme */
name?: string;
/** acme */
slug: string;
/** https://acme.cal.com */
fullDomain: string;
/** cal.com */
domainSuffix: string;
} & z.infer<typeof teamMetadataSchema>)
| null

View File

@ -121,7 +121,7 @@ export default function MemberListItem(props: Props) {
const bookerUrlWithoutProtocol = bookerUrl.replace(/^https?:\/\//, "");
const bookingLink = !!props.member.username && `${bookerUrlWithoutProtocol}/${props.member.username}`;
const isAdmin = props.team && ["ADMIN", "OWNER"].includes(props.team.membership?.role);
const appList = props.member.connectedApps.map(({ logo, name, externalId }) => {
const appList = props.member.connectedApps?.map(({ logo, name, externalId }) => {
return logo ? (
externalId ? (
<div className="ltr:mr-2 rtl:ml-2 ">

View File

@ -11,7 +11,6 @@ interface Props {
id?: number;
name?: string | null;
slug?: string | null;
logo?: string | null;
bio?: string | null;
hideBranding?: boolean | undefined;
role: MembershipRole;

View File

@ -1,5 +1,5 @@
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
@ -20,7 +20,6 @@ interface Props {
id?: number;
name?: string | null;
slug?: string | null;
logo?: string | null;
bio?: string | null;
hideBranding?: boolean | undefined;
role: MembershipRole;
@ -66,7 +65,7 @@ export default function TeamInviteListItem(props: Props) {
<div className="flex">
<Avatar
size="mdLg"
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
imageSrc={`${WEBAPP_URL}/team/${team.slug}/avatar.png`}
alt="Team Logo"
className=""
/>

View File

@ -149,7 +149,6 @@ const MembersView = () => {
{
id: team.id,
accepted: team.membership.accepted || false,
logo: team.logo,
name: team.name,
slug: team.slug,
role: team.membership.role,

View File

@ -94,7 +94,6 @@ const ProfileView = () => {
if (team) {
form.setValue("name", team.name || "");
form.setValue("slug", team.slug || "");
form.setValue("logo", team.logo || "");
form.setValue("bio", team.bio || "");
if (team.slug === null && (team?.metadata as Prisma.JsonObject)?.requestedSlug) {
form.setValue("slug", ((team?.metadata as Prisma.JsonObject)?.requestedSlug as string) || "");
@ -165,7 +164,6 @@ const ProfileView = () => {
handleSubmit={(values) => {
if (team) {
const variables = {
logo: values.logo,
name: values.name,
slug: values.slug,
bio: values.bio,

View File

@ -6,6 +6,7 @@ export const CALCOM_ENV = process.env.CALCOM_ENV || process.env.NODE_ENV;
export const IS_PRODUCTION = CALCOM_ENV === "production";
export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production";
/** https://app.cal.com */
export const WEBAPP_URL =
process.env.NEXT_PUBLIC_WEBAPP_URL ||
VERCEL_URL ||

View File

@ -15,19 +15,16 @@ export async function getTeamWithMembers(args: {
slug?: string;
userId?: number;
orgSlug?: string | null;
isTeamView?: boolean;
isOrgView?: boolean;
}) {
const { id, slug, userId, orgSlug } = args;
const { id, slug, userId, orgSlug, isTeamView, isOrgView } = args;
const userSelect = Prisma.validator<Prisma.UserSelect>()({
username: true,
email: true,
name: true,
id: true,
bio: true,
destinationCalendar: {
select: {
externalId: true,
},
},
teams: {
select: {
team: {
@ -37,7 +34,6 @@ export async function getTeamWithMembers(args: {
},
},
},
selectedCalendars: true,
credentials: {
select: {
app: {
@ -58,7 +54,6 @@ export async function getTeamWithMembers(args: {
id: true,
name: true,
slug: true,
logo: true,
bio: true,
hideBranding: true,
hideBookATeamMember: true,
@ -136,8 +131,9 @@ export async function getTeamWithMembers(args: {
// This should improve performance saving already app data found.
const appDataMap = new Map();
const members = team.members.map((obj) => {
const { credentials, ...restUser } = obj.user;
return {
...obj.user,
...restUser,
role: obj.role,
accepted: obj.accepted,
disableImpersonation: obj.disableImpersonation,
@ -145,24 +141,26 @@ export async function getTeamWithMembers(args: {
? obj.user.teams.filter((obj) => obj.team.slug !== orgSlug).map((obj) => obj.team.slug)
: null,
avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`,
connectedApps: obj?.user?.credentials?.map((cred) => {
const appSlug = cred.app?.slug;
let appData = appDataMap.get(appSlug);
connectedApps: !isTeamView
? credentials?.map((cred) => {
const appSlug = cred.app?.slug;
let appData = appDataMap.get(appSlug);
if (!appData) {
appData = getAppFromSlug(appSlug);
appDataMap.set(appSlug, appData);
}
if (!appData) {
appData = getAppFromSlug(appSlug);
appDataMap.set(appSlug, appData);
}
const isCalendar = cred?.app?.categories?.includes("calendar") ?? false;
const externalId = isCalendar ? cred.destinationCalendars?.[0]?.externalId : null;
return {
name: appData?.name ?? null,
logo: appData?.logo ?? null,
app: cred.app,
externalId: externalId ?? null,
};
}),
const isCalendar = cred?.app?.categories?.includes("calendar") ?? false;
const externalId = isCalendar ? cred.destinationCalendars?.[0]?.externalId : null;
return {
name: appData?.name ?? null,
logo: appData?.logo ?? null,
app: cred.app,
externalId: externalId ?? null,
};
})
: null,
};
});
@ -182,7 +180,7 @@ export async function getTeamWithMembers(args: {
token.expires > new Date(new Date().setHours(24))
),
metadata: teamMetadataSchema.parse(team.metadata),
eventTypes,
eventTypes: !isOrgView ? eventTypes : null,
members,
};
}

View File

@ -22,8 +22,13 @@ export function UnpublishedEntity(props: UnpublishedEntityProps) {
}
headline={t("team_is_unpublished", {
team: props.name,
defaultValue: `${props.name} is unpublished`,
})}
description={t(`${props.orgSlug ? "org" : "team"}_is_unpublished_description`, {
defaultValue: `This ${
props.orgSlug ? "organization" : "team"
} link is currently not available. Please contact the organization owner or ask them to publish it.`,
})}
description={t(`${props.orgSlug ? "org" : "team"}_is_unpublished_description`)}
/>
</div>
);