From c2ec95845e1f2af1120bd8f3fd8157727bdfe2ed Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 14 Aug 2024 09:22:40 +0800 Subject: [PATCH] request count of library items only when needed --- packages/api/src/resolvers/article/index.ts | 41 +++++++++++-------- .../api/src/resolvers/function_resolvers.ts | 17 ++++++++ .../web/lib/networking/library_items/gql.tsx | 4 +- .../library_items/useLibraryItems.tsx | 17 ++++---- packages/web/pages/settings/account.tsx | 26 +++++------- packages/web/pages/tools/bulk.tsx | 31 +++++++------- 6 files changed, 76 insertions(+), 60 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 44874c156..52bdfad0b 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -37,6 +37,7 @@ import { MutationSaveArticleReadingProgressArgs, MutationSetBookmarkArticleArgs, MutationSetFavoriteArticleArgs, + PageInfo, PageType, QueryArticleArgs, QuerySearchArgs, @@ -79,7 +80,8 @@ import { countLibraryItems, createOrUpdateLibraryItem, findLibraryItemsByPrefix, - searchAndCountLibraryItems, + SearchArgs, + searchLibraryItems, softDeleteLibraryItem, sortParamsToSort, updateLibraryItem, @@ -582,8 +584,15 @@ export const saveArticleReadingProgressResolver = authorized< export type PartialLibraryItem = Merge type PartialSearchItemEdge = Merge +export type PartialPageInfo = Merge< + PageInfo, + { searchLibraryItemArgs?: SearchArgs } +> export const searchResolver = authorized< - Merge }>, + Merge< + SearchSuccess, + { edges: Array; pageInfo: PartialPageInfo } + >, SearchError, QuerySearchArgs >(async (_obj, params, { uid }) => { @@ -595,18 +604,17 @@ export const searchResolver = authorized< return { errorCodes: [SearchErrorCode.QueryTooLong] } } - const { libraryItems, count } = await searchAndCountLibraryItems( - { - from: Number(startCursor), - size: first + 1, // fetch one more item to get next cursor - includePending: true, - includeContent: params.includeContent ?? true, // by default include content for offline use for now - includeDeleted: params.query?.includes('in:trash'), - query: params.query, - useFolders: params.query?.includes('use:folders'), - }, - uid - ) + const searchLibraryItemArgs = { + from: Number(startCursor), + size: first + 1, // fetch one more item to get next cursor + includePending: true, + includeContent: params.includeContent ?? true, // by default include content for offline use for now + includeDeleted: params.query?.includes('in:trash'), + query: params.query, + useFolders: params.query?.includes('use:folders'), + } + + const libraryItems = await searchLibraryItems(searchLibraryItemArgs, uid) const start = startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0 @@ -631,7 +639,7 @@ export const searchResolver = authorized< startCursor, hasNextPage, endCursor, - totalCount: count, + searchLibraryItemArgs, }, } }) @@ -675,7 +683,7 @@ export const updatesSinceResolver = authorized< folder ? ' in:' + folder : '' } sort:${sort.by}-${sort.order}` - const { libraryItems, count } = await searchAndCountLibraryItems( + const libraryItems = await searchLibraryItems( { from: Number(startCursor), size: size + 1, // fetch one more item to get next cursor @@ -714,7 +722,6 @@ export const updatesSinceResolver = authorized< startCursor, hasNextPage, endCursor, - totalCount: count, }, } }) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 3f9ef86ec..f38352c82 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -30,6 +30,7 @@ import { } from '../generated/graphql' import { getAISummary } from '../services/ai-summaries' import { findUserFeatures } from '../services/features' +import { countLibraryItems } from '../services/library_item' import { Merge } from '../util' import { isBase64Image, validatedDate, wordsCount } from '../utils/helpers' import { createImageProxyUrl } from '../utils/imageproxy' @@ -43,6 +44,7 @@ import { emptyTrashResolver, fetchContentResolver, PartialLibraryItem, + PartialPageInfo, } from './article' import { addDiscoverFeedResolver, @@ -588,6 +590,21 @@ export const functionResolvers = { highlightsCount: (item: LibraryItem) => item.highlightAnnotations?.length, ...readingProgressHandlers, }, + PageInfo: { + async totalCount( + pageInfo: PartialPageInfo, + _: unknown, + ctx: ResolverContext + ) { + if (pageInfo.totalCount) return pageInfo.totalCount + + if (pageInfo.searchLibraryItemArgs && ctx.claims) { + return countLibraryItems(pageInfo.searchLibraryItemArgs, ctx.claims.uid) + } + + return 0 + }, + }, Subscription: { newsletterEmail(subscription: Subscription) { return subscription.newsletterEmail?.address diff --git a/packages/web/lib/networking/library_items/gql.tsx b/packages/web/lib/networking/library_items/gql.tsx index 543b4c1c8..c5193056f 100644 --- a/packages/web/lib/networking/library_items/gql.tsx +++ b/packages/web/lib/networking/library_items/gql.tsx @@ -18,7 +18,7 @@ export const recommendationFragment = gql` } ` -export const GQL_SEARCH_QUERY = gql` +export const gqlSearchQuery = (includeTotalCount = false) => gql` query Search( $after: String $first: Int @@ -77,7 +77,7 @@ export const GQL_SEARCH_QUERY = gql` hasPreviousPage startCursor endCursor - totalCount + totalCount @include(if: ${includeTotalCount}) } } ... on SearchError { diff --git a/packages/web/lib/networking/library_items/useLibraryItems.tsx b/packages/web/lib/networking/library_items/useLibraryItems.tsx index 27d2762db..6ac065da6 100644 --- a/packages/web/lib/networking/library_items/useLibraryItems.tsx +++ b/packages/web/lib/networking/library_items/useLibraryItems.tsx @@ -1,4 +1,3 @@ -import { GraphQLClient } from 'graphql-request' import { QueryClient, useInfiniteQuery, @@ -6,25 +5,24 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { GraphQLClient } from 'graphql-request' +import { gqlEndpoint } from '../../appConfig' import { ContentReader, PageType, State } from '../fragments/articleFragment' import { Highlight } from '../fragments/highlightFragment' -import { requestHeaders } from '../networkHelpers' import { Label } from '../fragments/labelFragment' +import { requestHeaders } from '../networkHelpers' import { + gqlSearchQuery, GQL_BULK_ACTION, GQL_DELETE_LIBRARY_ITEM, - GQL_GET_LIBRARY_ITEM, GQL_GET_LIBRARY_ITEM_CONTENT, GQL_MOVE_ITEM_TO_FOLDER, GQL_SAVE_ARTICLE_READING_PROGRESS, GQL_SAVE_URL, - GQL_SEARCH_QUERY, GQL_SET_LABELS, GQL_SET_LINK_ARCHIVED, GQL_UPDATE_LIBRARY_ITEM, } from './gql' -import { gqlEndpoint } from '../../appConfig' -import { useState } from 'react' function gqlFetcher( query: string, @@ -213,7 +211,7 @@ export const insertItemInCache = ( export function useGetLibraryItems( folder: string | undefined, - { limit, searchQuery }: LibraryItemsQueryInput, + { limit, searchQuery, includeCount }: LibraryItemsQueryInput, enabled = true ) { const fullQuery = folder @@ -223,7 +221,7 @@ export function useGetLibraryItems( return useInfiniteQuery({ queryKey: ['libraryItems', fullQuery], queryFn: async ({ pageParam }) => { - const response = (await gqlFetcher(GQL_SEARCH_QUERY, { + const response = (await gqlFetcher(gqlSearchQuery(includeCount), { after: pageParam, first: limit, query: fullQuery, @@ -531,7 +529,7 @@ export function useRefreshProcessingItems() { itemIds: string[] }) => { const fullQuery = `in:all includes:${variables.itemIds.join(',')}` - const result = (await gqlFetcher(GQL_SEARCH_QUERY, { + const result = (await gqlFetcher(gqlSearchQuery(), { first: 10, query: fullQuery, includeContent: false, @@ -945,6 +943,7 @@ export type LibraryItemsQueryInput = { searchQuery?: string cursor?: string includeContent?: boolean + includeCount?: boolean } type LibraryItemsData = { diff --git a/packages/web/pages/settings/account.tsx b/packages/web/pages/settings/account.tsx index 612f9efeb..a86ad183e 100644 --- a/packages/web/pages/settings/account.tsx +++ b/packages/web/pages/settings/account.tsx @@ -1,11 +1,4 @@ -import { - ChangeEvent, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { Toaster } from 'react-hot-toast' import { Button } from '../../components/elements/Button' import { @@ -19,22 +12,22 @@ import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' import { SettingsLayout } from '../../components/templates/SettingsLayout' import { styled, theme } from '../../components/tokens/stitches.config' import { userHasFeature } from '../../lib/featureFlag' +import { useGetLibraryItems } from '../../lib/networking/library_items/useLibraryItems' import { emptyTrashMutation } from '../../lib/networking/mutations/emptyTrashMutation' +import { optInFeature } from '../../lib/networking/mutations/optIntoFeatureMutation' +import { scheduleDigest } from '../../lib/networking/mutations/scheduleDigest' +import { updateDigestConfigMutation } from '../../lib/networking/mutations/updateDigestConfigMutation' import { updateEmailMutation } from '../../lib/networking/mutations/updateEmailMutation' import { updateUserMutation } from '../../lib/networking/mutations/updateUserMutation' import { updateUserProfileMutation } from '../../lib/networking/mutations/updateUserProfileMutation' -import { useGetLibraryItems } from '../../lib/networking/library_items/useLibraryItems' -import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' -import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery' -import { applyStoredTheme } from '../../lib/themeUpdater' -import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' import { DigestChannel, useGetUserPersonalization, } from '../../lib/networking/queries/useGetUserPersonalization' -import { updateDigestConfigMutation } from '../../lib/networking/mutations/updateDigestConfigMutation' -import { scheduleDigest } from '../../lib/networking/mutations/scheduleDigest' -import { optInFeature } from '../../lib/networking/mutations/optIntoFeatureMutation' +import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' +import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery' +import { applyStoredTheme } from '../../lib/themeUpdater' +import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' const ACCOUNT_LIMIT = 50_000 @@ -103,6 +96,7 @@ export default function Account(): JSX.Element { limit: 0, searchQuery: '', sortDescending: false, + includeCount: true, }) const libraryCount = useMemo(() => { diff --git a/packages/web/pages/tools/bulk.tsx b/packages/web/pages/tools/bulk.tsx index 028d9a716..b5083f8a4 100644 --- a/packages/web/pages/tools/bulk.tsx +++ b/packages/web/pages/tools/bulk.tsx @@ -1,25 +1,23 @@ -import { useCallback, useEffect, useState } from 'react' -import { applyStoredTheme } from '../../lib/themeUpdater' - -import { VStack } from '../../components/elements/LayoutPrimitives' - -import { StyledText } from '../../components/elements/StyledText' -import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { BulkAction } from '../../lib/networking/library_items/useLibraryItems' -import { Button } from '../../components/elements/Button' -import { theme } from '../../components/tokens/stitches.config' -import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' -import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' import { useRouter } from 'next/router' -import { - useBulkActions, - useGetLibraryItems, -} from '../../lib/networking/library_items/useLibraryItems' +import { useCallback, useEffect, useState } from 'react' +import { Button } from '../../components/elements/Button' import { BorderedFormInput, FormLabel, } from '../../components/elements/FormElements' +import { VStack } from '../../components/elements/LayoutPrimitives' +import { StyledText } from '../../components/elements/StyledText' +import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { theme } from '../../components/tokens/stitches.config' import { DEFAULT_HOME_PATH } from '../../lib/navigations' +import { + BulkAction, + useBulkActions, + useGetLibraryItems, +} from '../../lib/networking/library_items/useLibraryItems' +import { applyStoredTheme } from '../../lib/themeUpdater' +import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' type RunningState = 'none' | 'confirming' | 'running' | 'completed' @@ -39,6 +37,7 @@ export default function BulkPerformer(): JSX.Element { searchQuery: query, limit: 1, sortDescending: false, + includeCount: true, }) useEffect(() => {