Skip to content

Commit

Permalink
feat(search): apply pt::text() when searching portable text fields
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars authored and bjoerge committed Apr 20, 2021
1 parent 53ddb27 commit ba30e92
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 12 deletions.
7 changes: 6 additions & 1 deletion packages/@sanity/base/src/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ import {versionedClient} from '../client/versionedClient'
import {getSearchableTypes} from './common/utils'
import {createWeightedSearch} from './weighted/createWeightedSearch'

export default createWeightedSearch(getSearchableTypes(schema), versionedClient)
// Use >= 2021-03-25 for pt::text() support
const searchClient = versionedClient.withConfig({
apiVersion: '2021-03-25',
})

export default createWeightedSearch(getSearchableTypes(schema), searchClient)
17 changes: 11 additions & 6 deletions packages/@sanity/base/src/search/weighted/createWeightedSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {joinPath} from '../../util/searchUtils'
import {removeDupes} from '../../util/draftUtils'
import {tokenize} from '../common/tokenize'
import {applyWeights} from './applyWeights'
import {WeightedHit, WeightedSearchOptions, SearchOptions} from './types'
import {WeightedHit, WeightedSearchOptions, SearchOptions, SearchPath} from './types'

const combinePaths = flow([flatten, union, compact])

Expand All @@ -20,6 +20,9 @@ const toGroqParams = (terms: string[]): Record<string, string> => {
}, params)
}

const pathWithMapper = ({mapWith, path}: SearchPath): string =>
mapWith ? `${mapWith}(${path})` : path

export function createWeightedSearch(
types: ObjectSchemaType[],
client: SanityClient,
Expand All @@ -31,17 +34,19 @@ export function createWeightedSearch(
paths: type.__experimental_search.map((config) => ({
weight: config.weight,
path: joinPath(config.path),
mapWith: config.mapWith,
})),
}))

const combinedSearchPaths = combinePaths(
searchSpec.map((configForType) => configForType.paths.map((opt) => opt.path))
searchSpec.map((configForType) => configForType.paths.map((opt) => pathWithMapper(opt)))
)

const selections = searchSpec.map(
(spec) =>
`_type == "${spec.typeName}" => {${spec.paths.map((cfg, i) => `"w${i}": ${cfg.path}`)}}`
)
const selections = searchSpec.map((spec) => {
const constraint = `_type == "${spec.typeName}" => `
const selection = `{ ${spec.paths.map((cfg, i) => `"w${i}": ${pathWithMapper(cfg)}`)} }`
return `${constraint}${selection}`
})

// this is the actual search function that takes the search string and returns the hits
return function search(queryString: string, searchOpts: SearchOptions = {}) {
Expand Down
11 changes: 10 additions & 1 deletion packages/@sanity/base/src/search/weighted/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
/**
* @internal
*/
export interface SearchPath {
weight: number
path: string
mapWith?: string
}

/**
* @internal
*/
export interface SearchSpec {
typeName: string
paths: {weight: number; path: string}[]
paths: SearchPath[]
}

/**
Expand Down
34 changes: 31 additions & 3 deletions packages/@sanity/schema/src/legacy/resolveSearchConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@ const stringFieldsSymbol = Symbol('__cachedStringFields')

const isReference = (type) => type.type && type.type.name === 'reference'

function reduceType(type, reducer, accumulator, path = [], maxDepth) {
const portableTextFields = ['style', 'list']
const isPortableTextBlock = (type) =>
type.name === 'block' || (type.type && isPortableTextBlock(type.type))
const isPortableTextArray = (type) =>
type.jsonType === 'array' && Array.isArray(type.of) && type.of.some(isPortableTextBlock)

function reduceType(type, reducer, acc, path = [], maxDepth) {
if (maxDepth < 0) {
return accumulator
return acc
}

const accumulator = reducer(acc, type, path)
if (type.jsonType === 'array' && Array.isArray(type.of)) {
return reduceArray(type, reducer, accumulator, path, maxDepth)
}

if (type.jsonType === 'object' && Array.isArray(type.fields) && !isReference(type)) {
return reduceObject(type, reducer, accumulator, path, maxDepth)
}
return reducer(accumulator, type, path)

return accumulator
}

function reduceArray(arrayType, reducer, accumulator, path, maxDepth) {
Expand All @@ -25,7 +35,13 @@ function reduceArray(arrayType, reducer, accumulator, path, maxDepth) {
}

function reduceObject(objectType, reducer, accumulator, path, maxDepth) {
const isPtBlock = isPortableTextBlock(objectType)
return objectType.fields.reduce((acc, field) => {
// Don't include styles and list types as searchable paths for portable text blocks
if (isPtBlock && portableTextFields.includes(field.name)) {
return acc
}

const segment = [field.name].concat(field.type.jsonType === 'array' ? [[]] : [])
return reduceType(field.type, reducer, acc, path.concat(segment), maxDepth - 1)
}, accumulator)
Expand Down Expand Up @@ -59,6 +75,11 @@ function getCachedStringFieldPaths(type, maxDepth) {
...BASE_WEIGHTS,
...deriveFromPreview(type),
...getStringFieldPaths(type, maxDepth).map((path) => ({weight: 1, path})),
...getPortableTextFieldPaths(type, maxDepth).map((path) => ({
weight: 1,
path,
mapWith: 'pt::text',
})),
],
(spec) => spec.path.join('.')
)
Expand All @@ -73,6 +94,13 @@ function getStringFieldPaths(type, maxDepth) {
return reduceType(type, reducer, [], [], maxDepth)
}

function getPortableTextFieldPaths(type, maxDepth) {
const reducer = (accumulator, childType, path) =>
isPortableTextArray(childType) ? [...accumulator, path] : accumulator

return reduceType(type, reducer, [], [], maxDepth)
}

export default function resolveSearchConfig(type) {
return getCachedStringFieldPaths(type, 4)
}
1 change: 1 addition & 0 deletions packages/@sanity/schema/src/legacy/types/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const normalizeSearchConfig = (configs) => {
return {
weight: 'weight' in conf ? conf.weight : 1,
path: toPath(conf.path),
mapWith: typeof conf.mapWith === 'string' ? conf.mapWith : undefined,
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@sanity/types/src/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export interface ObjectSchemaType extends BaseSchemaType {

// Experimentals
/* eslint-disable camelcase */
__experimental_search?: {path: string; weight: number}[]
__experimental_search?: {path: string; weight: number; mapWith?: string}[]
/* eslint-enable camelcase */
}

Expand Down

0 comments on commit ba30e92

Please sign in to comment.