diff --git a/packages/app-store/_appRegistry.ts b/packages/app-store/_appRegistry.ts index 709b665ddb..81a4665d55 100644 --- a/packages/app-store/_appRegistry.ts +++ b/packages/app-store/_appRegistry.ts @@ -1,3 +1,5 @@ +import { z } from "zod"; + import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { getAppFromSlug } from "@calcom/app-store/utils"; import prisma, { safeAppSelect, safeCredentialSelect } from "@calcom/prisma"; @@ -35,6 +37,7 @@ export async function getAppRegistry() { select: { dirName: true, slug: true, categories: true, enabled: true }, }); const apps = [] as App[]; + const mostPopularApps = await getMostPopularApps(); for await (const dbapp of dbApps) { const app = await getAppWithMetadata(dbapp); if (!app) continue; @@ -47,6 +50,7 @@ export async function getAppRegistry() { category: app.category || "other", installed: true /* All apps from DB are considered installed by default. @TODO: Add and filter our by `enabled` property */, + installCount: mostPopularApps[dbapp.slug] || 0, }); } return apps; @@ -82,6 +86,7 @@ export async function getAppRegistryWithCredentials(userId: number) { credentials: Credential[]; isDefault?: boolean; })[]; + const mostPopularApps = await getMostPopularApps(); for await (const dbapp of dbApps) { const app = await getAppWithMetadata(dbapp); if (!app) continue; @@ -108,6 +113,7 @@ export async function getAppRegistryWithCredentials(userId: number) { categories: dbapp.categories, credentials: dbapp.credentials, installed: true, + installCount: mostPopularApps[dbapp.slug] || 0, isDefault: usersDefaultApp === dbapp.slug, ...(app.dependencies && { dependencyData }), }); @@ -115,3 +121,25 @@ export async function getAppRegistryWithCredentials(userId: number) { return apps; } + +async function getMostPopularApps() { + const mostPopularApps = z.array(z.object({ appId: z.string(), installCount: z.number() })).parse( + await prisma.$queryRaw` + SELECT + c."appId", + COUNT(*)::integer AS "installCount" + FROM + "Credential" c + WHERE + c."appId" IS NOT NULL + GROUP BY + c."appId" + ORDER BY + "installCount" DESC + ` + ); + return mostPopularApps.reduce((acc, { appId, installCount }) => { + acc[appId] = installCount; + return acc; + }, {} as Record); +} diff --git a/packages/types/App.d.ts b/packages/types/App.d.ts index 56381f7386..31c39385b3 100644 --- a/packages/types/App.d.ts +++ b/packages/types/App.d.ts @@ -149,6 +149,8 @@ export type AppFrontendPayload = Omit & { name?: string; installed?: boolean; }[]; + /** Number of users who currently have this App installed */ + installCount?: number; }; export type AppMeta = App; diff --git a/packages/ui/components/apps/PopularAppsSlider.tsx b/packages/ui/components/apps/PopularAppsSlider.tsx index 260f60bb97..71fcfb3b61 100644 --- a/packages/ui/components/apps/PopularAppsSlider.tsx +++ b/packages/ui/components/apps/PopularAppsSlider.tsx @@ -10,7 +10,7 @@ export const PopularAppsSlider = ({ items }: { items: T[] }) => { return ( title={t("most_popular")} - items={items.filter((app) => !!app.trending)} + items={items.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))} itemKey={(app) => app.name} options={{ perView: 3,