From 3204d0af347155fb7a5db502c91d483336131bd8 Mon Sep 17 00:00:00 2001 From: Abhijeet Singh Date: Thu, 31 Aug 2023 06:50:31 +0530 Subject: [PATCH] feat: team admin: see connected apps of team members (#11036) * added feature:team admin can see connected apps of members * fixed the type error * Update packages/lib/server/queries/teams/index.ts * Minor fixes --------- Co-authored-by: alannnc --- .../ee/teams/components/MemberListItem.tsx | 21 ++++++- packages/lib/server/queries/teams/index.ts | 60 +++++++++++++++---- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx index 7271e5f5fb..7b6b89c2c0 100644 --- a/packages/features/ee/teams/components/MemberListItem.tsx +++ b/packages/features/ee/teams/components/MemberListItem.tsx @@ -109,7 +109,22 @@ export default function MemberListItem(props: Props) { const bookerUrl = useBookerUrl(); 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 }) => { + return logo ? ( + externalId ? ( +
+ + {`${name} + +
+ ) : ( +
+ {`${name} +
+ ) + ) : null; + }); return (
  • @@ -124,9 +139,9 @@ export default function MemberListItem(props: Props) {
    - {name} - + {name} {!props.member.accepted && } + {isAdmin && props.member.accepted && appList} {props.member.role && }
    diff --git a/packages/lib/server/queries/teams/index.ts b/packages/lib/server/queries/teams/index.ts index cad927703d..bfac687cbe 100644 --- a/packages/lib/server/queries/teams/index.ts +++ b/packages/lib/server/queries/teams/index.ts @@ -1,8 +1,10 @@ import { Prisma } from "@prisma/client"; +import { getAppFromSlug } from "@calcom/app-store/utils"; +import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; import { getSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains"; import prisma, { baseEventTypeSelect } from "@calcom/prisma"; -import { SchedulingType } from "@calcom/prisma/enums"; +import { AppCategories, SchedulingType } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { WEBAPP_URL } from "../../../constants"; @@ -22,6 +24,17 @@ export async function getTeamWithMembers(args: { name: true, id: true, bio: true, + destinationCalendar: { + select: { + externalId: true, + }, + }, + selectedCalendars: true, + credentials: { + include: { + app: true, + }, + }, }); const teamSelect = Prisma.validator()({ id: true, @@ -111,16 +124,43 @@ export async function getTeamWithMembers(args: { }); if (!team) return null; - const members = team.members.map((obj) => { - return { - ...obj.user, - role: obj.role, - accepted: obj.accepted, - disableImpersonation: obj.disableImpersonation, - avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`, - }; - }); + const members = await Promise.all( + team.members.map(async (obj) => { + const calendarCredentials = getCalendarCredentials(obj.user.credentials); + const { connectedCalendars } = await getConnectedCalendars( + calendarCredentials, + obj.user.selectedCalendars, + obj.user.destinationCalendar?.externalId + ); + const connectedApps = obj.user.credentials + .map(({ app, id }) => { + const appMetaData = getAppFromSlug(app?.slug); + + if (app?.categories.includes(AppCategories.calendar)) { + const externalId = connectedCalendars.find((cal) => cal.credentialId == id)?.primary?.email; + return { name: appMetaData?.name, logo: appMetaData?.logo, slug: appMetaData?.slug, externalId }; + } + return { name: appMetaData?.name, logo: appMetaData?.logo, slug: appMetaData?.slug }; + }) + .sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? "")); + // Prevent credentials from leaking to frontend + const { + credentials: _credentials, + destinationCalendar: _destinationCalendar, + selectedCalendars: _selectedCalendars, + ...rest + } = { + ...obj.user, + role: obj.role, + accepted: obj.accepted, + disableImpersonation: obj.disableImpersonation, + avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`, + connectedApps, + }; + return rest; + }) + ); const eventTypes = team.eventTypes.map((eventType) => ({ ...eventType, metadata: EventTypeMetaDataSchema.parse(eventType.metadata),