fix: public sharing performance improvements (#1841)

This commit is contained in:
Philip Okugbe
2026-01-13 16:00:22 +00:00
committed by GitHub
parent 47097969a0
commit 0bbc1c35de
4 changed files with 63 additions and 59 deletions
@@ -269,12 +269,15 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
const prefetchPage = () => { const prefetchPage = () => {
timerRef.current = setTimeout(() => { timerRef.current = setTimeout(async () => {
queryClient.prefetchQuery({ const page = await queryClient.fetchQuery({
queryKey: ["pages", node.data.slugId], queryKey: ["pages", node.data.id],
queryFn: () => getPageById({ pageId: node.data.slugId }), queryFn: () => getPageById({ pageId: node.data.id }),
staleTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000,
}); });
if (page?.slugId) {
queryClient.setQueryData(["pages", page.slugId], page);
}
}, 150); }, 150);
}; };
@@ -8,7 +8,6 @@ import {
Switch, Switch,
Text, Text,
TextInput, TextInput,
Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { IconExternalLink, IconWorld, IconLock } from "@tabler/icons-react"; import { IconExternalLink, IconWorld, IconLock } from "@tabler/icons-react";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
@@ -21,12 +20,12 @@ import {
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import { extractPageSlugId, getPageIcon } from "@/lib"; import { extractPageSlugId, getPageIcon } from "@/lib";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { usePageQuery } from "@/features/page/queries/page-query.ts";
import CopyTextButton from "@/components/common/copy.tsx"; import CopyTextButton from "@/components/common/copy.tsx";
import { getAppUrl, isCloud } from "@/lib/config.ts"; import { getAppUrl, isCloud } from "@/lib/config.ts";
import { buildPageUrl } from "@/features/page/page.utils.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts";
import classes from "@/features/share/components/share.module.css"; import classes from "@/features/share/components/share.module.css";
import useTrial from "@/ee/hooks/use-trial.tsx"; import useTrial from "@/ee/hooks/use-trial.tsx";
import { getCheckoutLink } from "@/ee/billing/services/billing-service.ts";
interface ShareModalProps { interface ShareModalProps {
readOnly: boolean; readOnly: boolean;
@@ -35,7 +34,9 @@ export default function ShareModal({ readOnly }: ShareModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { pageSlug } = useParams(); const { pageSlug } = useParams();
const pageId = extractPageSlugId(pageSlug); const pageSlugId = extractPageSlugId(pageSlug);
const { data: page } = usePageQuery({ pageId: pageSlugId });
const pageId = page?.id;
const { data: share } = useShareForPageQuery(pageId); const { data: share } = useShareForPageQuery(pageId);
const { spaceSlug } = useParams(); const { spaceSlug } = useParams();
const { isTrial } = useTrial(); const { isTrial } = useTrial();
@@ -27,9 +27,7 @@ import {
getShares, getShares,
updateShare, updateShare,
} from "@/features/share/services/share-service.ts"; } from "@/features/share/services/share-service.ts";
import { IPage } from "@/features/page/types/page.types.ts";
import { IPagination, QueryParams } from "@/lib/types.ts"; import { IPagination, QueryParams } from "@/lib/types.ts";
import { useEffect } from "react";
export function useGetSharesQuery( export function useGetSharesQuery(
params?: QueryParams, params?: QueryParams,
@@ -72,7 +70,7 @@ export function useShareForPageQuery(
queryKey: ["share-for-page", pageId], queryKey: ["share-for-page", pageId],
queryFn: () => getShareForPage(pageId), queryFn: () => getShareForPage(pageId),
enabled: !!pageId, enabled: !!pageId,
staleTime: 0, staleTime: 60 * 1000,
retry: false, retry: false,
}); });
+51 -49
View File
@@ -123,80 +123,82 @@ export class ShareService {
.withRecursive('page_hierarchy', (cte) => .withRecursive('page_hierarchy', (cte) =>
cte cte
.selectFrom('pages') .selectFrom('pages')
.leftJoin('shares', 'shares.pageId', 'pages.id')
.select([ .select([
'id', 'pages.id',
'slugId', 'pages.slugId',
'pages.title', 'pages.title',
'pages.icon', 'pages.icon',
'parentPageId', 'pages.parentPageId',
sql`0`.as('level'), sql`0`.as('level'),
'shares.id as shareId',
'shares.key as shareKey',
'shares.includeSubPages',
'shares.searchIndexing',
'shares.creatorId',
'shares.spaceId',
'shares.workspaceId',
'shares.createdAt',
]) ])
.where(isValidUUID(pageId) ? 'id' : 'slugId', '=', pageId) .where(isValidUUID(pageId) ? 'pages.id' : 'pages.slugId', '=', pageId)
.where('deletedAt', 'is', null) .where('pages.deletedAt', 'is', null)
.unionAll((union) => .unionAll(
union (union) =>
.selectFrom('pages as p') union
.select([ .selectFrom('pages as p')
'p.id', .innerJoin('page_hierarchy as ph', 'ph.parentPageId', 'p.id')
'p.slugId', .leftJoin('shares as s', 's.pageId', 'p.id')
'p.title', .select([
'p.icon', 'p.id',
'p.parentPageId', 'p.slugId',
// Increase the level by 1 for each ancestor. 'p.title',
sql`ph.level + 1`.as('level'), 'p.icon',
]) 'p.parentPageId',
.innerJoin('page_hierarchy as ph', 'ph.parentPageId', 'p.id') sql`ph.level + 1`.as('level'),
.where('p.deletedAt', 'is', null), 's.id as shareId',
's.key as shareKey',
's.includeSubPages',
's.searchIndexing',
's.creatorId',
's.spaceId',
's.workspaceId',
's.createdAt',
])
.where('p.deletedAt', 'is', null)
.where(sql`ph.share_id`, 'is', null) // stop if share found
.where(sql`ph.level`, '<', sql`25`), // prevent loop
), ),
) )
.selectFrom('page_hierarchy') .selectFrom('page_hierarchy')
.leftJoin('shares', 'shares.pageId', 'page_hierarchy.id') .selectAll()
.select([ .where('shareId', 'is not', null)
'page_hierarchy.id as sharedPageId', .limit(1)
'page_hierarchy.slugId as sharedPageSlugId',
'page_hierarchy.title as sharedPageTitle',
'page_hierarchy.icon as sharedPageIcon',
'page_hierarchy.level as level',
'shares.id',
'shares.key',
'shares.pageId',
'shares.includeSubPages',
'shares.searchIndexing',
'shares.creatorId',
'shares.spaceId',
'shares.workspaceId',
'shares.createdAt',
'shares.updatedAt',
])
.where('shares.id', 'is not', null)
.orderBy('page_hierarchy.level', 'asc')
.executeTakeFirst(); .executeTakeFirst();
if (!share || share.workspaceId != workspaceId) { if (!share || share.workspaceId !== workspaceId) {
return undefined; return undefined;
} }
if (share.level === 1 && !share.includeSubPages) { if ((share.level as number) > 0 && !share.includeSubPages) {
// we can only show a page if its shared ancestor permits it
return undefined; return undefined;
} }
return { return {
id: share.id, id: share.shareId,
key: share.key, key: share.shareKey,
includeSubPages: share.includeSubPages, includeSubPages: share.includeSubPages,
searchIndexing: share.searchIndexing, searchIndexing: share.searchIndexing,
pageId: share.pageId, pageId: share.id,
creatorId: share.creatorId, creatorId: share.creatorId,
spaceId: share.spaceId, spaceId: share.spaceId,
workspaceId: share.workspaceId, workspaceId: share.workspaceId,
createdAt: share.createdAt, createdAt: share.createdAt,
level: share.level, level: share.level,
sharedPage: { sharedPage: {
id: share.sharedPageId, id: share.id,
slugId: share.sharedPageSlugId, slugId: share.slugId,
title: share.sharedPageTitle, title: share.title,
icon: share.sharedPageIcon, icon: share.icon,
}, },
}; };
} }