request count of library items only when needed

This commit is contained in:
Hongbo Wu
2024-08-14 09:22:40 +08:00
parent 3a6dbf9032
commit c2ec95845e
6 changed files with 76 additions and 60 deletions

View File

@ -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<LibraryItem, { format?: string }>
type PartialSearchItemEdge = Merge<SearchItemEdge, { node: PartialLibraryItem }>
export type PartialPageInfo = Merge<
PageInfo,
{ searchLibraryItemArgs?: SearchArgs }
>
export const searchResolver = authorized<
Merge<SearchSuccess, { edges: Array<PartialSearchItemEdge> }>,
Merge<
SearchSuccess,
{ edges: Array<PartialSearchItemEdge>; 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,
},
}
})

View File

@ -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

View File

@ -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 {

View File

@ -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 = {

View File

@ -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(() => {

View File

@ -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(() => {