Skip to content

Commit

Permalink
Merge pull request #2108 from dubinc/links-count-optimization
Browse files Browse the repository at this point in the history
Links Count Optimization
  • Loading branch information
steven-tey authored Mar 2, 2025
2 parents c63f043 + e36acdc commit 1f625ab
Show file tree
Hide file tree
Showing 44 changed files with 331 additions and 178 deletions.
17 changes: 15 additions & 2 deletions apps/web/app/api/links/count/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getFolderIdsToFilter } from "@/lib/analytics/get-folder-ids-to-filter";
import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { DubApiError } from "@/lib/api/errors";
import { getLinksCount } from "@/lib/api/links";
import { withWorkspace } from "@/lib/auth";
import { verifyFolderAccess } from "@/lib/folder/permissions";
Expand All @@ -18,27 +19,39 @@ export const GET = withWorkspace(
}

if (folderId) {
await verifyFolderAccess({
const selectedFolder = await verifyFolderAccess({
workspace,
userId: session.user.id,
folderId,
requiredPermission: "folders.read",
});
if (selectedFolder.type === "mega") {
throw new DubApiError({
code: "bad_request",
message: "Cannot get links count for mega folders.",
});
}
}

/* we only need to get the folder ids if we are:
- not filtering by folder
- there's a groupBy
- filtering by search, domain, or tags
*/
const folderIds =
let folderIds =
!folderId && (groupBy || search || domain || tagId || tagIds || tagNames)
? await getFolderIdsToFilter({
workspace,
userId: session.user.id,
})
: undefined;

if (Array.isArray(folderIds)) {
folderIds = folderIds?.filter((id) => id !== "");
if (folderIds.length === 0) {
folderIds = undefined;
}
}
const count = await getLinksCount({
searchParams: params,
workspaceId: workspace.id,
Expand Down
14 changes: 12 additions & 2 deletions apps/web/app/api/links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getLinksQuerySchemaExtended,
linkEventSchema,
} from "@/lib/zod/schemas/links";
import { Folder } from "@dub/prisma/client";
import { LOCALHOST_IP } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import { NextResponse } from "next/server";
Expand All @@ -27,8 +28,9 @@ export const GET = withWorkspace(
await getDomainOrThrow({ workspace, domain });
}

let selectedFolder: Pick<Folder, "id" | "type"> | null = null;
if (folderId) {
await verifyFolderAccess({
selectedFolder = await verifyFolderAccess({
workspace,
userId: session.user.id,
folderId,
Expand All @@ -40,18 +42,26 @@ export const GET = withWorkspace(
- not filtering by folder
- filtering by search, domain, or tags
*/
const folderIds =
let folderIds =
!folderId && (search || domain || tagId || tagIds || tagNames)
? await getFolderIdsToFilter({
workspace,
userId: session.user.id,
})
: undefined;

if (Array.isArray(folderIds)) {
folderIds = folderIds?.filter((id) => id !== "");
if (folderIds.length === 0) {
folderIds = undefined;
}
}

const response = await getLinksForWorkspace({
...params,
workspaceId: workspace.id,
folderIds,
searchMode: selectedFolder?.type === "mega" ? "exact" : "fuzzy",
});

return NextResponse.json(response, {
Expand Down
110 changes: 57 additions & 53 deletions apps/web/app/app.dub.co/(dashboard)/[slug]/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useCheckFolderPermission,
useFolderPermissions,
} from "@/lib/swr/use-folder-permissions";
import { useIsMegaFolder } from "@/lib/swr/use-is-mega-folder";
import useLinks from "@/lib/swr/use-links";
import useWorkspace from "@/lib/swr/use-workspace";
import { RequestFolderEditAccessButton } from "@/ui/folders/request-edit-button";
Expand Down Expand Up @@ -76,6 +77,7 @@ function WorkspaceLinks() {
} = useLinkFilters();

const folderId = searchParams.get("folderId");
const { isMegaFolder } = useIsMegaFolder();

const { isLoading } = useFolderPermissions();
const canCreateLinks = useCheckFolderPermission(
Expand All @@ -91,61 +93,63 @@ function WorkspaceLinks() {
<MaxWidthWrapper className="flex flex-col gap-y-3">
<div className="flex flex-wrap items-center justify-between gap-2 lg:flex-nowrap">
<div className="flex w-full grow gap-2 md:w-auto">
<div className="grow basis-0 md:grow-0">
<Filter.Select
filters={filters}
activeFilters={activeFilters}
onSelect={onSelect}
onRemove={onRemove}
onSearchChange={setSearch}
onSelectedFilterChange={setSelectedFilter}
className="w-full"
emptyState={{
tagIds: (
<div className="flex flex-col items-center gap-2 p-2 text-center text-sm">
<div className="flex items-center justify-center rounded-2xl border border-neutral-200 bg-neutral-50 p-3">
<Tag className="size-6 text-neutral-700" />
</div>
<p className="mt-2 font-medium text-neutral-950">
No tags found
</p>
<p className="mx-auto mt-1 w-full max-w-[180px] text-neutral-700">
Add tags to organize your links
</p>
<div>
<Button
className="mt-1 h-8"
onClick={() => setShowAddEditTagModal(true)}
text="Add tag"
/>
{!isMegaFolder && (
<div className="grow basis-0 md:grow-0">
<Filter.Select
filters={filters}
activeFilters={activeFilters}
onSelect={onSelect}
onRemove={onRemove}
onSearchChange={setSearch}
onSelectedFilterChange={setSelectedFilter}
className="w-full"
emptyState={{
tagIds: (
<div className="flex flex-col items-center gap-2 p-2 text-center text-sm">
<div className="flex items-center justify-center rounded-2xl border border-neutral-200 bg-neutral-50 p-3">
<Tag className="size-6 text-neutral-700" />
</div>
<p className="mt-2 font-medium text-neutral-950">
No tags found
</p>
<p className="mx-auto mt-1 w-full max-w-[180px] text-neutral-700">
Add tags to organize your links
</p>
<div>
<Button
className="mt-1 h-8"
onClick={() => setShowAddEditTagModal(true)}
text="Add tag"
/>
</div>
</div>
</div>
),
domain: (
<div className="flex flex-col items-center gap-2 p-2 text-center text-sm">
<div className="flex items-center justify-center rounded-2xl border border-neutral-200 bg-neutral-50 p-3">
<Globe className="size-6 text-neutral-700" />
),
domain: (
<div className="flex flex-col items-center gap-2 p-2 text-center text-sm">
<div className="flex items-center justify-center rounded-2xl border border-neutral-200 bg-neutral-50 p-3">
<Globe className="size-6 text-neutral-700" />
</div>
<p className="mt-2 font-medium text-neutral-950">
No domains found
</p>
<p className="mx-auto mt-1 w-full max-w-[180px] text-neutral-700">
Add a custom domain to match your brand
</p>
<div>
<Button
className="mt-1 h-8"
onClick={() =>
router.push(`/${slug}/settings/domains`)
}
text="Add domain"
/>
</div>
</div>
<p className="mt-2 font-medium text-neutral-950">
No domains found
</p>
<p className="mx-auto mt-1 w-full max-w-[180px] text-neutral-700">
Add a custom domain to match your brand
</p>
<div>
<Button
className="mt-1 h-8"
onClick={() =>
router.push(`/${slug}/settings/domains`)
}
text="Add domain"
/>
</div>
</div>
),
}}
/>
</div>
),
}}
/>
</div>
)}
<div className="grow basis-0 md:grow-0">
<LinkDisplay />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DUB_PARTNERS_ANALYTICS_INTERVAL } from "@/lib/analytics/constants";
import { formatDateTooltip } from "@/lib/analytics/format-date-tooltip";
import { IntervalOptions } from "@/lib/analytics/types";
import useProgramRevenue from "@/lib/swr/use-program-analytics";
Expand All @@ -24,7 +25,7 @@ export function OverviewChart() {
const {
start,
end,
interval = "30d",
interval = DUB_PARTNERS_ANALYTICS_INTERVAL,
} = searchParamsObj as {
start?: string;
end?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import useFolders from "@/lib/swr/use-folders";
import useFoldersCount from "@/lib/swr/use-folders-count";
import useLinksCount from "@/lib/swr/use-links-count";
import useWorkspace from "@/lib/swr/use-workspace";
import { Folder } from "@/lib/types";
import { FOLDERS_MAX_PAGE_SIZE } from "@/lib/zod/schemas/folders";
Expand All @@ -16,6 +15,7 @@ import { useRouter, useSearchParams } from "next/navigation";
const allLinkFolder: Folder = {
id: "unsorted",
name: "Links",
type: "default",
accessLevel: null,
linkCount: 0,
createdAt: new Date(),
Expand All @@ -37,12 +37,6 @@ export const FoldersPageClient = () => {
includeParams: true,
});

const { data: allLinksCount } = useLinksCount({
query: {
showArchived: true,
},
});

const showAllLinkFolder =
!searchParams.get("search") || folders?.length === 0;

Expand Down Expand Up @@ -79,11 +73,7 @@ export const FoldersPageClient = () => {
))
) : (
<>
{showAllLinkFolder && (
<FolderCard
folder={{ ...allLinkFolder, linkCount: allLinksCount }}
/>
)}
{showAllLinkFolder && <FolderCard folder={allLinkFolder} />}
{folders?.map((folder) => (
<FolderCard key={folder.id} folder={folder} />
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { DUB_PARTNERS_ANALYTICS_INTERVAL } from "@/lib/analytics/constants";
import { formatDateTooltip } from "@/lib/analytics/format-date-tooltip";
import { IntervalOptions } from "@/lib/analytics/types";
import usePartnerEarningsCount from "@/lib/swr/use-partner-earnings-count";
Expand Down Expand Up @@ -46,7 +47,7 @@ export function EarningsCompositeChart() {
const {
start,
end,
interval = "30d",
interval = DUB_PARTNERS_ANALYTICS_INTERVAL,
groupBy = "linkId",
} = searchParamsObj as {
start?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"use client";

import { intervals } from "@/lib/analytics/constants";
import {
DUB_PARTNERS_ANALYTICS_INTERVAL,
intervals,
} from "@/lib/analytics/constants";
import { IntervalOptions } from "@/lib/analytics/types";
import usePartnerLinks from "@/lib/swr/use-partner-links";
import { usePartnerLinkModal } from "@/ui/modals/partner-link-modal";
Expand Down Expand Up @@ -36,7 +39,7 @@ export function ProgramLinksPageClient() {
const {
start,
end,
interval = "30d",
interval = DUB_PARTNERS_ANALYTICS_INTERVAL,
} = searchParamsObj as {
start?: string;
end?: string;
Expand All @@ -62,7 +65,7 @@ export function ProgramLinksPageClient() {
<SimpleDateRangePicker
className="w-fit"
align="start"
defaultInterval="30d"
defaultInterval={DUB_PARTNERS_ANALYTICS_INTERVAL}
/>
{["dub", "acme"].includes(programSlug) && (
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { DUB_PARTNERS_ANALYTICS_INTERVAL } from "@/lib/analytics/constants";
import { formatDateTooltip } from "@/lib/analytics/format-date-tooltip";
import { IntervalOptions } from "@/lib/analytics/types";
import { useSyncedLocalStorage } from "@/lib/hooks/use-synced-local-storage";
Expand Down Expand Up @@ -46,7 +47,7 @@ const ProgramOverviewContext = createContext<{
interval: IntervalOptions;
color?: string;
}>({
interval: "30d",
interval: DUB_PARTNERS_ANALYTICS_INTERVAL,
});

export default function ProgramPageClient() {
Expand All @@ -64,7 +65,7 @@ export default function ProgramPageClient() {
const {
start,
end,
interval = "30d",
interval = DUB_PARTNERS_ANALYTICS_INTERVAL,
} = searchParamsObj as {
start?: string;
end?: string;
Expand Down
3 changes: 3 additions & 0 deletions apps/web/lib/analytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const eventIntervals = [
"all",
] as const;

export const DUB_LINKS_ANALYTICS_INTERVAL = "24h";
export const DUB_PARTNERS_ANALYTICS_INTERVAL = "30d";

export const INTERVAL_DISPLAYS = [
{
display: "Last 24 hours",
Expand Down
1 change: 1 addition & 0 deletions apps/web/lib/analytics/get-folder-ids-to-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const getFolderIdsToFilter = async ({
const folders = await getFolders({
workspaceId: workspace.id,
userId,
excludeBulkFolders: true,
});

folderIds = folders.map((folder) => folder.id).concat("");
Expand Down
11 changes: 11 additions & 0 deletions apps/web/lib/api/links/bulk-delete-links.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { storage } from "@/lib/storage";
import { recordLinkTB, transformLinkTB } from "@/lib/tinybird";
import { prisma } from "@dub/prisma";
import { R2_URL } from "@dub/utils";
import { linkCache } from "./cache";
import { ExpandedLink } from "./utils";
Expand All @@ -25,5 +26,15 @@ export async function bulkDeleteLinks(links: ExpandedLink[]) {
links
.filter((link) => link.image?.startsWith(`${R2_URL}/images/${link.id}`))
.map((link) => storage.delete(link.image!.replace(`${R2_URL}/`, ""))),

// Update totalLinks for the workspace
prisma.project.update({
where: {
id: links[0].projectId!,
},
data: {
totalLinks: { decrement: links.length },
},
}),
]);
}
Loading

0 comments on commit 1f625ab

Please sign in to comment.