Skip to content

Commit ab7c0f4

Browse files
committedMar 7, 2025·
fix(sanity): allow discovery of all document versions using groq2024 search (#8775)
1 parent 757fd32 commit ab7c0f4

File tree

4 files changed

+42
-69
lines changed

4 files changed

+42
-69
lines changed
 

‎packages/sanity/src/core/search/groq2024/createGroq2024Search.ts

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ function getSearchTerms(
2323
}
2424

2525
/**
26+
* Note: When using the `raw` persepctive, `groq2024` may emit uncollated documents, manifesting as
27+
* duplicate search results. Consumers must collate the results.
28+
*
2629
* @internal
2730
*/
2831
export const createGroq2024Search: SearchStrategyFactory<Groq2024SearchResults> = (

‎packages/sanity/src/core/search/groq2024/createSearchQuery.test.ts

+18-36
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('createSearchQuery', () => {
4444
expect(query).toMatchInlineSnapshot(
4545
`
4646
"// findability-mvi:5
47-
*[_type in $__types && !(_id in path("versions.**"))] | score(boost(_type in ["basic-schema-test"] && title match text::query($__query), 10), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
47+
*[_type in $__types] | score(boost(_type in ["basic-schema-test"] && title match text::query($__query), 10), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
4848
`,
4949
)
5050

@@ -58,19 +58,20 @@ describe('createSearchQuery', () => {
5858

5959
describe('searchOptions', () => {
6060
it('should include drafts by default', () => {
61-
const {options} = createSearchQuery(
61+
const {query, options} = createSearchQuery(
6262
{
6363
query: 'term0',
6464
types: [testType],
6565
},
6666
'',
6767
)
6868

69-
expect(options.perspective).toBe('previewDrafts')
69+
expect(query).not.toMatch(`!(_id in path('drafts.**'))`)
70+
expect(options.perspective).toBe('raw')
7071
})
7172

7273
it('should exclude drafts when configured', () => {
73-
const {options} = createSearchQuery(
74+
const {query, options} = createSearchQuery(
7475
{
7576
query: 'term0',
7677
types: [testType],
@@ -79,50 +80,33 @@ describe('createSearchQuery', () => {
7980
{includeDrafts: false},
8081
)
8182

82-
expect(options.perspective).toBe('published')
83+
expect(query).toMatch(`!(_id in path('drafts.**'))`)
84+
expect(options.perspective).toBe('raw')
8385
})
8486

85-
it('should give `perspective` precedence over `includeDrafts`', () => {
86-
const {options: optionsIncludeDrafts} = createSearchQuery(
87-
{
88-
query: 'term0',
89-
types: [testType],
90-
},
91-
'',
92-
{
93-
includeDrafts: true,
94-
perspective: 'published',
95-
},
96-
)
97-
98-
expect(optionsIncludeDrafts.perspective).toBe('published')
99-
100-
const {options: optionsExcludeDrafts} = createSearchQuery(
87+
it('should use `raw` perspective when no perspective provided', () => {
88+
const {options} = createSearchQuery(
10189
{
10290
query: 'term0',
10391
types: [testType],
10492
},
10593
'',
106-
{
107-
includeDrafts: false,
108-
perspective: 'drafts',
109-
},
11094
)
11195

112-
expect(optionsExcludeDrafts.perspective).toBe('drafts')
96+
expect(options.perspective).toBe('raw')
11397
})
11498

115-
it('should add no perspective parameter when `raw` perspective provided', () => {
99+
it('should use `raw` perspective when empty perspective array provided', () => {
116100
const {options} = createSearchQuery(
117101
{
118102
query: 'term0',
119103
types: [testType],
120104
},
121105
'',
122-
{perspective: 'raw'},
106+
{perspective: []},
123107
)
124108

125-
expect(options.perspective).toBeUndefined()
109+
expect(options.perspective).toBe('raw')
126110
})
127111

128112
it('should use provided limit (plus one to determine existence of next page)', () => {
@@ -150,9 +134,7 @@ describe('createSearchQuery', () => {
150134
{filter: 'randomCondition == $customParam', params: {customParam: 'custom'}},
151135
)
152136

153-
expect(query).toContain(
154-
'*[_type in $__types && (randomCondition == $customParam) && !(_id in path("versions.**"))]',
155-
)
137+
expect(query).toContain('*[_type in $__types && (randomCondition == $customParam)]')
156138
expect(params.customParam).toEqual('custom')
157139
})
158140

@@ -188,7 +170,7 @@ describe('createSearchQuery', () => {
188170

189171
expect(query).toMatchInlineSnapshot(`
190172
"// findability-mvi:5
191-
*[_type in $__types && [@, _id] match text::query($__query) && !(_id in path("versions.**"))] | order(exampleField desc) [0...$__limit] {exampleField, _type, _id, _originalId}"
173+
*[_type in $__types && [@, _id] match text::query($__query)] | order(exampleField desc) [0...$__limit] {exampleField, _type, _id, _originalId}"
192174
`)
193175

194176
expect(query).toContain('| order(exampleField desc)')
@@ -222,7 +204,7 @@ describe('createSearchQuery', () => {
222204

223205
expect(query).toMatchInlineSnapshot(`
224206
"// findability-mvi:5
225-
*[_type in $__types && [@, _id] match text::query($__query) && !(_id in path("versions.**"))] | order(exampleField desc,anotherExampleField asc,lower(mapWithField) asc) [0...$__limit] {exampleField, anotherExampleField, mapWithField, _type, _id, _originalId}"
207+
*[_type in $__types && [@, _id] match text::query($__query)] | order(exampleField desc,anotherExampleField asc,lower(mapWithField) asc) [0...$__limit] {exampleField, anotherExampleField, mapWithField, _type, _id, _originalId}"
226208
`)
227209

228210
expect(query).toContain(
@@ -241,7 +223,7 @@ describe('createSearchQuery', () => {
241223

242224
expect(query).toMatchInlineSnapshot(`
243225
"// findability-mvi:5
244-
*[_type in $__types && !(_id in path("versions.**"))] | score(boost(_type in ["basic-schema-test"] && title match text::query($__query), 10), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
226+
*[_type in $__types] | score(boost(_type in ["basic-schema-test"] && title match text::query($__query), 10), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
245227
`)
246228

247229
expect(query).toContain('| order(_score desc)')
@@ -319,7 +301,7 @@ describe('createSearchQuery', () => {
319301

320302
expect(query).toMatchInlineSnapshot(`
321303
"// findability-mvi:5
322-
*[_type in $__types && !(_id in path("versions.**"))] | score(boost(_type in ["numbers-in-path"] && cover[].cards[].title match text::query($__query), 5), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
304+
*[_type in $__types] | score(boost(_type in ["numbers-in-path"] && cover[].cards[].title match text::query($__query), 5), [@, _id] match text::query($__query)) | order(_score desc) [_score > 0] [0...$__limit] {_score, _type, _id, _originalId}"
323305
`)
324306

325307
expect(query).toContain('cover[].cards[].title match text::query($__query), 5)')

‎packages/sanity/src/core/search/groq2024/createSearchQuery.ts

+14-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {type ClientPerspective} from '@sanity/client'
12
import {DEFAULT_MAX_FIELD_DEPTH} from '@sanity/schema/_internal'
23
import {type CrossDatasetType, type SchemaType} from '@sanity/types'
34
import {groupBy} from 'lodash'
@@ -68,8 +69,6 @@ export function createSearchQuery(
6869
)
6970
.filter(({paths}) => paths.length !== 0)
7071

71-
const isRaw = isPerspectiveRaw(perspective)
72-
7372
// Note: Computing this is unnecessary when `!isScored`.
7473
const flattenedSpecs = specs
7574
.map(({typeName, paths}) => paths.map((path) => ({...path, typeName})))
@@ -93,13 +92,26 @@ export function createSearchQuery(
9392
const sortOrder = options?.sort ?? [{field: '_score', direction: 'desc'}]
9493
const isScored = sortOrder.some(({field}) => field === '_score')
9594

95+
let activePerspective: ClientPerspective | undefined = perspective
96+
97+
// No perspective, or empty perspective array, provided.
98+
if (
99+
typeof perspective === 'undefined' ||
100+
(Array.isArray(perspective) && perspective.length === 0)
101+
) {
102+
activePerspective = 'raw'
103+
}
104+
105+
const isRaw = isPerspectiveRaw(activePerspective)
106+
96107
const filters: string[] = [
97108
'_type in $__types',
98109
// If the search request doesn't use scoring, directly filter documents.
99110
isScored ? [] : baseMatch,
100111
options.filter ? `(${options.filter})` : [],
101112
searchTerms.filter ? `(${searchTerms.filter})` : [],
102113
isRaw ? [] : '!(_id in path("versions.**"))',
114+
includeDrafts === false ? `!(_id in path('drafts.**'))` : [],
103115
options.cursor ?? [],
104116
].flat()
105117

@@ -130,22 +142,6 @@ export function createSearchQuery(
130142
.map((s) => `// ${s}`)
131143
.join('\n')
132144

133-
let activePerspective: string | string[] | undefined
134-
135-
switch (true) {
136-
// Raw perspective provided.
137-
case isRaw:
138-
activePerspective = undefined
139-
break
140-
// Any other perspective provided.
141-
case typeof perspective !== 'undefined':
142-
activePerspective = perspective
143-
break
144-
// No perspective provided.
145-
default:
146-
activePerspective = includeDrafts ? 'previewDrafts' : 'published'
147-
}
148-
149145
return {
150146
query: [pragma, query].join('\n'),
151147
options: {

‎packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.ts

+7-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import {
2-
type CurrentUser,
3-
type SanityDocumentLike,
4-
type SchemaType,
5-
type SearchStrategy,
6-
} from '@sanity/types'
1+
import {type CurrentUser, type SchemaType, type SearchStrategy} from '@sanity/types'
72

83
import {type SearchHit, type SearchTerms} from '../../../../../../search'
9-
import {collate} from '../../../../../../util'
4+
import {removeDupes} from '../../../../../../util/draftUtils'
105
import {type RecentSearch} from '../../datastores/recentSearches'
116
import {type SearchFieldDefinitionDictionary} from '../../definitions/fields'
127
import {type SearchFilterDefinitionDictionary} from '../../definitions/filters'
@@ -231,9 +226,11 @@ export function searchReducer(state: SearchReducerState, action: SearchAction):
231226
...state.result,
232227
error: null,
233228
hasLocal: true,
234-
hits: collateHits(
235-
state.result.hasLocal ? [...state.result.hits, ...action.hits] : action.hits,
236-
),
229+
hits: state.result.hasLocal
230+
? removeDupes([...state.result.hits, ...action.hits].map(({hit}) => hit)).map(
231+
(hit) => ({hit}),
232+
)
233+
: action.hits,
237234
loaded: true,
238235
loading: false,
239236
},
@@ -597,8 +594,3 @@ function stripRecent(terms: RecentSearch | SearchTerms) {
597594
}
598595
return terms
599596
}
600-
601-
function collateHits(hits: SearchHit[]) {
602-
const collated = collate(hits.map((h) => h.hit))
603-
return collated.map((doc) => ({hit: (doc.draft || doc.published)! as SanityDocumentLike}))
604-
}

0 commit comments

Comments
 (0)
Please sign in to comment.