Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add Text Search API search strategy #5785

Merged
merged 46 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
03d1fb6
feat(core): add Text Search API search strategy
juice49 Feb 14, 2024
ff7acd9
feat(core): support multiple search strategies in `createSearch` func…
juice49 Feb 14, 2024
76068e7
feat(core): use generic `createSearch` function for navbar search
juice49 Feb 14, 2024
18f8783
feat(core): use generic `createSearch` function for reference search
juice49 Feb 14, 2024
8d1568b
feat(structure): very unsound prototype of Text Search API strategy f…
juice49 Feb 14, 2024
711a40a
feat(core): export Text Search API types
juice49 Feb 14, 2024
a000747
feat(core): add type filtering to Text Search API request
juice49 Feb 15, 2024
06d754a
feat(core): remove unused search weighting for Text Search API search…
juice49 Feb 15, 2024
0d1903f
feat(core): support filtering in Text Search API strategy
juice49 Feb 15, 2024
cd07099
refactor(core): specialise `createSearchQuery` function for Text Sear…
juice49 Feb 15, 2024
23ab9e9
feat(core): add `TextSearchParams.params` type
juice49 Feb 15, 2024
99e6327
fix(core): allow `TextSearchParams.params` to include any single or a…
juice49 Feb 15, 2024
6fe331a
refactor(core): return `TextSearchParams` directly
juice49 Feb 15, 2024
8b3b771
feat(core): add limit to Text Search API search strategy
juice49 Feb 15, 2024
94ccb2a
fix(core): allow non-weighted search hit
juice49 Feb 15, 2024
9043577
refactor(core): remove unused function
juice49 Feb 15, 2024
4bc01de
fix(core): allow non-weighted search hit
juice49 Feb 15, 2024
90ab597
refactor(core): remove unused function
juice49 Feb 15, 2024
bfcc5e1
feat(core): scaffold hybrid search strategy
juice49 Feb 16, 2024
350f329
refactor(core): rename for clarity
juice49 Feb 16, 2024
2715ec0
chore(core): remove irrelevant comment
juice49 Feb 16, 2024
4fd3ffa
feat(core): add `search.__experimental_strategy` option for controlli…
juice49 Feb 16, 2024
e43a27b
feat(test-studio): enable Text Search API search strategy
juice49 Feb 16, 2024
4eb36d2
feat: implement hybrid approach (messy)
ricokahler Feb 19, 2024
68c38e4
refactor(core): rename for clarity
juice49 Feb 19, 2024
b371257
feat(core): add `useDocumentSearch` stub
juice49 Feb 27, 2024
28dccf0
feat(core): prototype Text Search API pagination with global search
juice49 Feb 26, 2024
b17ac28
feat(core): support new result data shape in weighted search strategy
juice49 Feb 28, 2024
6647370
refactor(core): refine search result type
juice49 Feb 28, 2024
a221d57
fix(core): allow reference search to work with new data shape
juice49 Feb 28, 2024
24a0236
refactor(core): improve search strategy types
juice49 Feb 28, 2024
dcff44d
refactor(core): improve search strategy types
juice49 Feb 28, 2024
ddc32dc
refactor: remove hybrid search and clean up
ricokahler Feb 29, 2024
3d2b465
refactor(core): rename binding
juice49 Feb 29, 2024
4ac2f1d
refactor(core): remove unused offset-based pagination, fix pagination…
juice49 Mar 1, 2024
8b99d9f
fix(core): use new data shape
juice49 Mar 1, 2024
feab6a7
test(core): remove nonexistent option
juice49 Mar 1, 2024
a8bcc14
fix(core): deduplicate search results
juice49 Mar 1, 2024
6e2cec5
refactor(core): remove unused `useDocumentSearch` hook prototype
juice49 Mar 1, 2024
8dc6243
test(core): global search pagination
juice49 Mar 4, 2024
408dca2
test(core): add assertion
juice49 Mar 4, 2024
c57e5a6
refactor(core): add type parameter for Text Search API hit attributes
juice49 Mar 4, 2024
a145723
refactor(core): remove unused offset
juice49 Mar 4, 2024
142118f
refactor(core): remove unused search offset handling
juice49 Mar 4, 2024
c430eef
feat(core): increase default search limit
juice49 Mar 4, 2024
9785477
chore(core): add clarification
juice49 Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ const sharedSettings = definePlugin({
assetSources: [imageAssetSource],
},
},
search: {
unstable_enableNewSearch: true,
},

i18n: {
bundles: testStudioLocaleBundles,
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/types/src/reference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type ReferenceFilterSearchOptions = {
params?: Record<string, unknown>
tag?: string
maxFieldDepth?: number
unstable_enableNewSearch?: boolean
}

/** @public */
Expand Down
2 changes: 1 addition & 1 deletion packages/sanity/src/_internal/browser.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export {getSearchableTypes, getSearchTypesWithMaxDepth} from '../core/search'
export {createSearch, getSearchableTypes, getSearchTypesWithMaxDepth} from '../core/search'
export {useSearchMaxFieldDepth} from '../core/studio/components/navbar/search/hooks/useSearchMaxFieldDepth'
7 changes: 7 additions & 0 deletions packages/sanity/src/core/config/configPropertyReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,10 @@ export const partialIndexingEnabledReducer = (opts: {

return result
}

export const newSearchEnabledReducer: ConfigPropertyReducer<boolean, ConfigContext> = (
prev,
{search},
): boolean => {
return prev || search?.unstable_enableNewSearch || false
}
8 changes: 8 additions & 0 deletions packages/sanity/src/core/config/prepareConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
initialDocumentBadges,
initialLanguageFilter,
newDocumentOptionsResolver,
newSearchEnabledReducer,
partialIndexingEnabledReducer,
resolveProductionUrlReducer,
schemaTemplatesReducer,
Expand Down Expand Up @@ -569,6 +570,13 @@ function resolveSource({
initialValue: config.search?.unstable_partialIndexing?.enabled ?? false,
}),
},
unstable_enableNewSearch: resolveConfigProperty({
config,
context,
reducer: newSearchEnabledReducer,
propertyName: 'search.unstable_enableNewSearch',
initialValue: false,
}),
// we will use this when we add search config to PluginOptions
/*filters: resolveConfigProperty({
config,
Expand Down
12 changes: 12 additions & 0 deletions packages/sanity/src/core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,16 @@ export interface PluginOptions {
unstable_partialIndexing?: {
enabled: boolean
}
/**
* Enables the experimental new search API as an opt-in feature. This flag
* allows you to test and provide feedback on the new search capabilities
* before they become the default search mechanism. It is part of an
* experimental set of features that are subject to change. Users should be
* aware that while this feature is in use, they may encounter
* inconsistencies or unexpected behavior compared to the stable search
* functionality.
*/
unstable_enableNewSearch?: boolean
}
}

Expand Down Expand Up @@ -707,6 +717,8 @@ export interface Source {
unstable_partialIndexing?: {
enabled: boolean
}

unstable_enableNewSearch?: boolean
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
type Path,
type Reference,
type ReferenceFilterSearchOptions,
type ReferenceOptions,
type ReferenceSchemaType,
type SanityDocument,
} from '@sanity/types'
import {type Path, type Reference, type ReferenceSchemaType} from '@sanity/types'
import * as PathUtils from '@sanity/util/paths'
import {
type ComponentProps,
Expand All @@ -15,16 +8,10 @@ import {
useMemo,
useRef,
} from 'react'
import {from, throwError} from 'rxjs'
import {catchError, mergeMap} from 'rxjs/operators'

import {type Source} from '../../../config'
import {type FIXME} from '../../../FIXME'
import {useSchema} from '../../../hooks'
import {useDocumentPreviewStore} from '../../../store'
import {useSource} from '../../../studio'
import {useSearchMaxFieldDepth} from '../../../studio/components/navbar/search/hooks/useSearchMaxFieldDepth'
import {DEFAULT_STUDIO_CLIENT_OPTIONS} from '../../../studioClient'
import {isNonNullable} from '../../../util'
import {useFormValue} from '../../contexts/FormValue'
import {useReferenceInputOptions} from '../../studio'
Expand All @@ -37,37 +24,6 @@ function useValueRef<T>(value: T): {current: T} {
return ref
}

interface SearchError {
message: string
details?: {
type: string
description: string
}
}

// eslint-disable-next-line require-await
async function resolveUserDefinedFilter(
options: ReferenceOptions | undefined,
document: SanityDocument,
valuePath: Path,
getClient: Source['getClient'],
): Promise<ReferenceFilterSearchOptions> {
if (!options) {
return {}
}

if (typeof options.filter === 'function') {
const parentPath = valuePath.slice(0, -1)
const parent = PathUtils.get(document, parentPath) as Record<string, unknown>
return options.filter({document, parentPath, parent, getClient})
}

return {
filter: options.filter,
params: 'filterParams' in options ? options.filterParams : undefined,
}
}

interface Options {
path: Path
schemaType: ReferenceSchemaType
Expand All @@ -76,12 +32,8 @@ interface Options {

export function useReferenceInput(options: Options) {
const {path, schemaType} = options
const source = useSource()
const client = source.getClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
const schema = useSchema()
const documentPreviewStore = useDocumentPreviewStore()
const maxFieldDepth = useSearchMaxFieldDepth()
const searchClient = useMemo(() => client.withConfig({apiVersion: '2021-03-25'}), [client])
const {EditReferenceLinkComponent, onEditReference, activePath, initialValueTemplateItems} =
useReferenceInputOptions()

Expand All @@ -95,32 +47,6 @@ export function useReferenceInput(options: Options) {
}, [documentTypeName, schema])

const disableNew = schemaType.options?.disableNew === true
const getClient = source.getClient

const handleSearch = useCallback(
(searchString: string) =>
from(resolveUserDefinedFilter(schemaType.options, documentRef.current, path, getClient)).pipe(
mergeMap(({filter, params}) =>
adapter.referenceSearch(searchClient, searchString, schemaType, {
...schemaType.options,
filter,
params,
tag: 'search.reference',
maxFieldDepth,
}),
),

catchError((err: SearchError) => {
const isQueryError = err.details && err.details.type === 'queryParseError'
if (schemaType.options?.filter && isQueryError) {
err.message = `Invalid reference filter, please check the custom "filter" option`
}
return throwError(err)
}),
),

[documentRef, path, searchClient, schemaType, maxFieldDepth, getClient],
)

const template = options.value?._strengthenOnPublish?.template
const EditReferenceLink = useMemo(
Expand Down Expand Up @@ -193,7 +119,6 @@ export function useReferenceInput(options: Options) {

return {
selectedState,
handleSearch,
isCurrentDocumentLiveEdit,
handleEditReference,
EditReferenceLink,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {combineLatest, type Observable, of} from 'rxjs'
import {map, mergeMap, startWith, switchMap} from 'rxjs/operators'

import {type DocumentPreviewStore, getPreviewPaths, prepareForPreview} from '../../../../preview'
import {createWeightedSearch, getSearchTypesWithMaxDepth} from '../../../../search'
import {createSearch, getSearchTypesWithMaxDepth} from '../../../../search'
import {collate, type CollatedHit, getDraftId, getIdPair, isRecord} from '../../../../util'
import {type ReferenceInfo, type ReferenceSearchHit} from '../../../inputs/ReferenceInput/types'

Expand Down Expand Up @@ -191,14 +191,14 @@ export function referenceSearch(
textTerm: string,
type: ReferenceSchemaType,
options: ReferenceFilterSearchOptions,
unstable_enableNewSearch: boolean,
): Observable<ReferenceSearchHit[]> {
const searchWeighted = createWeightedSearch(
getSearchTypesWithMaxDepth(type.to, options.maxFieldDepth),
client,
options,
)
return searchWeighted(textTerm, {includeDrafts: true}).pipe(
map((results) => results.map((result) => result.hit)),
const search = createSearch(getSearchTypesWithMaxDepth(type.to, options.maxFieldDepth), client, {
...options,
unstable_enableNewSearch,
})
return search(textTerm, {includeDrafts: true}).pipe(
map(({hits}) => hits.map(({hit}) => hit)),
map(collate),
// pick the 100 best matches
map((collated) => collated.slice(0, 100)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function StudioCrossDatasetReferenceInput(props: StudioCrossDatasetRefere
const client = source.getClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
const documentPreviewStore = useDocumentPreviewStore()
const getClient = source.getClient
const {unstable_enableNewSearch = false} = source.search

const crossDatasetClient = useMemo(() => {
return (
Expand Down Expand Up @@ -109,6 +110,7 @@ export function StudioCrossDatasetReferenceInput(props: StudioCrossDatasetRefere
params,
tag: 'search.cross-dataset-reference',
maxFieldDepth,
unstable_enableNewSearch,
}),
),

Expand All @@ -121,7 +123,15 @@ export function StudioCrossDatasetReferenceInput(props: StudioCrossDatasetRefere
}),
),

[crossDatasetClient, documentRef, path, schemaType, maxFieldDepth, getClient],
[
schemaType,
documentRef,
path,
getClient,
crossDatasetClient,
maxFieldDepth,
unstable_enableNewSearch,
],
)

const getReferenceInfo = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import {type Observable} from 'rxjs'
import {map} from 'rxjs/operators'

import {createWeightedSearch} from '../../../../../search'
import {createSearch} from '../../../../../search'
import {collate} from '../../../../../util'

interface SearchHit {
Expand All @@ -22,7 +22,7 @@ export function search(
type: CrossDatasetReferenceSchemaType,
options: ReferenceFilterSearchOptions,
): Observable<SearchHit[]> {
const searchWeighted = createWeightedSearch(
const searchWeighted = createSearch(
type.to.map((crossDatasetType) => ({
name: crossDatasetType.type,
// eslint-disable-next-line camelcase
Expand All @@ -31,14 +31,12 @@ export function search(
options.maxFieldDepth,
),
})),

client,
options,
)

return searchWeighted(textTerm, {includeDrafts: false}).pipe(
// pick the 100 best matches
map((results) => results.map((result) => result.hit)),
map(({hits}) => hits.map(({hit}) => hit)),
map(collate),
map((collated) =>
collated.map((entry) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function StudioReferenceInput(props: StudioReferenceInputProps) {
const {path, schemaType} = props
const {EditReferenceLinkComponent, onEditReference, activePath, initialValueTemplateItems} =
useReferenceInputOptions()
const {unstable_enableNewSearch = false} = source.search

const documentValue = useFormValue([]) as FIXME
const documentRef = useValueRef(documentValue)
Expand All @@ -110,13 +111,19 @@ export function StudioReferenceInput(props: StudioReferenceInputProps) {
(searchString: string) =>
from(resolveUserDefinedFilter(schemaType.options, documentRef.current, path, getClient)).pipe(
mergeMap(({filter, params}) =>
adapter.referenceSearch(searchClient, searchString, schemaType, {
...schemaType.options,
filter,
params,
tag: 'search.reference',
maxFieldDepth,
}),
adapter.referenceSearch(
searchClient,
searchString,
schemaType,
{
...schemaType.options,
filter,
params,
tag: 'search.reference',
maxFieldDepth,
},
unstable_enableNewSearch,
),
),

catchError((err: SearchError) => {
Expand All @@ -128,7 +135,15 @@ export function StudioReferenceInput(props: StudioReferenceInputProps) {
}),
),

[documentRef, path, searchClient, schemaType, maxFieldDepth, getClient],
[
schemaType,
documentRef,
path,
getClient,
searchClient,
maxFieldDepth,
unstable_enableNewSearch,
],
)

const template = props.value?._strengthenOnPublish?.template
Expand Down
3 changes: 1 addition & 2 deletions packages/sanity/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ export * from './preview'
export * from './schema'
export type {
SearchableType,
SearchFactoryOptions,
SearchOptions,
SearchSort,
SearchTerms,
WeightedSearchOptions,
} from './search'
export {createSearchQuery} from './search'
export * from './store'
export * from './studio'
export * from './studioClient'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {describe, expect, it} from '@jest/globals'
import {Schema} from '@sanity/schema'

import {getSearchableTypes} from '../common/utils'
import {getSearchableTypes} from '../common'
import {getSearchTypesWithMaxDepth} from './getSearchTypesWithMaxDepth'

const mockSchema = Schema.compile({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {resolveSearchConfig} from '@sanity/schema/_internal'
import {type ObjectSchemaType} from '@sanity/types'

import {type SearchableType} from './types'
import {type SearchableType} from '../common'

/**
* @internal
Expand Down
3 changes: 3 additions & 0 deletions packages/sanity/src/core/search/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './getSearchableTypes'
export * from './getSearchTypesWithMaxDepth'
export * from './types'
Loading
Loading