Migrate all search to v2 and remove v1 (#11125)

This commit is contained in:
DS Boyce
2026-07-09 15:08:20 -07:00
committed by GitHub
parent 65e10f13f7
commit 3499d3646e
14 changed files with 114 additions and 475 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} f
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/queries/notifications/feed'
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed'
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts-v2'
import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/state/queries/usePostThread/queryCache'
import {castAsShadow, type Shadow} from './types'
export type {Shadow} from './types'
+42 -8
View File
@@ -1,7 +1,13 @@
import {useCallback, useMemo, useRef} from 'react'
import {type AppBskyFeedSearchPostsV2, moderatePost} from '@atproto/api'
import {
type AppBskyFeedDefs,
type AppBskyFeedSearchPostsV2,
AtUri,
moderatePost,
} from '@atproto/api'
import {
type InfiniteData,
type QueryClient,
type QueryKey,
useInfiniteQuery,
} from '@tanstack/react-query'
@@ -13,14 +19,12 @@ import {
buildSearchPostsV2Filters,
extractSearchPostsParams,
} from './search-posts-params'
import {
didOrHandleUriMatches,
embedViewRecordToPostView,
getEmbeddedPost,
} from './util'
/**
* V2 search shares the `'search-posts'` query-key root with the original hook
* (src/state/queries/search-posts.ts) so the shadow-cache generators there -
* findAllPostsInQueryData / findAllProfilesInQueryData - discover V2 results
* too. This module is only used behind the AdvancedSearchV2Enable gate; the
* original hook is unchanged.
*/
const searchPostsQueryKeyRoot = 'search-posts'
const searchPostsV2QueryKey = ({
query,
@@ -164,3 +168,33 @@ export function useSearchPostsV2Query({
),
})
}
export function* findAllPostsInQueryData(
queryClient: QueryClient,
uri: string,
): Generator<AppBskyFeedDefs.PostView, undefined> {
const queryDatas = queryClient.getQueriesData<
InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>
>({
queryKey: [searchPostsQueryKeyRoot],
})
const atUri = new AtUri(uri)
for (const [_queryKey, queryData] of queryDatas) {
if (!queryData?.pages) {
continue
}
for (const page of queryData?.pages) {
for (const post of page.posts) {
if (didOrHandleUriMatches(atUri, post)) {
yield post
}
const quotedPost = getEmbeddedPost(post.embed)
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
yield embedViewRecordToPostView(quotedPost)
}
}
}
}
}
-209
View File
@@ -1,209 +0,0 @@
import {useCallback, useMemo, useRef} from 'react'
import {
type AppBskyActorDefs,
type AppBskyFeedDefs,
type AppBskyFeedSearchPosts,
AtUri,
moderatePost,
} from '@atproto/api'
import {
type InfiniteData,
type QueryClient,
type QueryKey,
useInfiniteQuery,
} from '@tanstack/react-query'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAgent} from '#/state/session'
import {
didOrHandleUriMatches,
embedViewRecordToPostView,
getEmbeddedPost,
} from './util'
const searchPostsQueryKeyRoot = 'search-posts'
const searchPostsQueryKey = ({
query,
sort,
author,
}: {
query: string
sort?: string
author?: string
}) => [searchPostsQueryKeyRoot, query, sort, author]
export function useSearchPostsQuery({
query,
sort,
enabled,
author,
}: {
query: string
sort?: 'top' | 'latest'
enabled?: boolean
author?: string
}) {
const agent = useAgent()
const moderationOpts = useModerationOpts()
const selectArgs = useMemo(
() => ({
isSearchingSpecificUser: !!author || /from:(\w+)/.test(query),
moderationOpts,
}),
[query, author, moderationOpts],
)
const lastRun = useRef<{
data: InfiniteData<AppBskyFeedSearchPosts.OutputSchema>
args: typeof selectArgs
result: InfiniteData<AppBskyFeedSearchPosts.OutputSchema>
} | null>(null)
return useInfiniteQuery<
AppBskyFeedSearchPosts.OutputSchema,
Error,
InfiniteData<AppBskyFeedSearchPosts.OutputSchema>,
QueryKey,
string | undefined
>({
queryKey: searchPostsQueryKey({query, sort, author}),
queryFn: async ({pageParam}) => {
const res = await agent.app.bsky.feed.searchPosts({
q: query,
limit: 25,
cursor: pageParam,
sort,
author,
})
return res.data
},
initialPageParam: undefined,
getNextPageParam: lastPage => lastPage.cursor,
enabled: enabled ?? !!moderationOpts,
select: useCallback(
(data: InfiniteData<AppBskyFeedSearchPosts.OutputSchema>) => {
const {moderationOpts, isSearchingSpecificUser} = selectArgs
/*
* If a user applies the `from:<user>` filter, don't apply any
* moderation. Note that if we add any more filtering logic below, we
* may need to adjust this.
*/
if (isSearchingSpecificUser) {
return data
}
// Keep track of the last run and whether we can reuse
// some already selected pages from there.
let reusedPages = []
if (lastRun.current) {
const {
data: lastData,
args: lastArgs,
result: lastResult,
} = lastRun.current
let canReuse = true
for (let key in selectArgs) {
if (selectArgs.hasOwnProperty(key)) {
if (
(selectArgs as Record<string, unknown>)[key] !==
(lastArgs as Record<string, unknown>)[key]
) {
// Can't do reuse anything if any input has changed.
canReuse = false
break
}
}
}
if (canReuse) {
for (let i = 0; i < data.pages.length; i++) {
if (data.pages[i] && lastData.pages[i] === data.pages[i]) {
reusedPages.push(lastResult.pages[i])
continue
}
// Stop as soon as pages stop matching up.
break
}
}
}
const result = {
...data,
pages: [
...reusedPages,
...data.pages.slice(reusedPages.length).map(page => {
return {
...page,
posts: page.posts.filter(post => {
const mod = moderatePost(post, moderationOpts!)
return !mod.ui('contentList').filter
}),
}
}),
],
}
lastRun.current = {data, result, args: selectArgs}
return result
},
[selectArgs],
),
})
}
export function* findAllPostsInQueryData(
queryClient: QueryClient,
uri: string,
): Generator<AppBskyFeedDefs.PostView, undefined> {
const queryDatas = queryClient.getQueriesData<
InfiniteData<AppBskyFeedSearchPosts.OutputSchema>
>({
queryKey: [searchPostsQueryKeyRoot],
})
const atUri = new AtUri(uri)
for (const [_queryKey, queryData] of queryDatas) {
if (!queryData?.pages) {
continue
}
for (const page of queryData?.pages) {
for (const post of page.posts) {
if (didOrHandleUriMatches(atUri, post)) {
yield post
}
const quotedPost = getEmbeddedPost(post.embed)
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
yield embedViewRecordToPostView(quotedPost)
}
}
}
}
}
export function* findAllProfilesInQueryData(
queryClient: QueryClient,
did: string,
): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> {
const queryDatas = queryClient.getQueriesData<
InfiniteData<AppBskyFeedSearchPosts.OutputSchema>
>({
queryKey: [searchPostsQueryKeyRoot],
})
for (const [_queryKey, queryData] of queryDatas) {
if (!queryData?.pages) {
continue
}
for (const page of queryData?.pages) {
for (const post of page.posts) {
if (post.author.did === did) {
yield post.author
}
const quotedPost = getEmbeddedPost(post.embed)
if (quotedPost?.author.did === did) {
yield quotedPost.author
}
}
}
}
}
@@ -19,7 +19,7 @@ import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} f
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/queries/notifications/feed'
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed'
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts-v2'
import {usePostThreadContext} from '#/state/queries/usePostThread'
import {getBranch} from '#/state/queries/usePostThread/traversal'
import {