Skip to content

Commit

Permalink
feat(sanity): adopt bundlePerspective for listing documents
Browse files Browse the repository at this point in the history
  • Loading branch information
juice49 committed Sep 6, 2024
1 parent c82fb68 commit da3f744
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 83 deletions.
2 changes: 2 additions & 0 deletions packages/sanity/src/core/search/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type SearchOptions = {
cursor?: string
limit?: number
perspective?: string
bundlePerspective?: string
isCrossDataset?: boolean
queryType?: 'prefixLast' | 'prefixNone'
}
Expand Down Expand Up @@ -190,6 +191,7 @@ export type TextSearchParams = {
*/
order?: TextSearchOrder[]
perspective?: string
bundlePerspective?: string
}

export type TextSearchResponse<Attributes = Record<string, unknown>> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,14 @@ export const createTextSearch: SearchStrategyFactory<TextSearchResults> = (
searchOptions.includeDrafts === false && "!(_id in path('drafts.**'))",
factoryOptions.filter ? `(${factoryOptions.filter})` : false,
searchTerms.filter ? `(${searchTerms.filter})` : false,
// Versions are collated server-side using the `bundlePerspective` option. Therefore, they
// must not be fetched individually.
'!(_id in path("versions.**"))',
].filter((baseFilter): baseFilter is string => Boolean(baseFilter))

const textSearchParams: TextSearchParams = {
perspective: searchOptions.perspective,
bundlePerspective: searchOptions.bundlePerspective,
query: {
string: getQueryString(searchTerms.query, searchOptions),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('createSearchQuery', () => {

expect(query).toEqual(
`// findability-mvi:${FINDABILITY_MVI}\n` +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0)]' +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && !(_id in path("versions.**"))]' +
'| order(_id asc)' +
'[0...$__limit]' +
'{_type, _id, _version, ...select(_type == "basic-schema-test" => { "w0": _id,"w1": _type,"w2": title })}',
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('createSearchQuery', () => {
})

expect(query).toContain(
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0 || object.field match $t0)]',
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0 || object.field match $t0) && !(_id in path("versions.**"))]',
)
})

Expand All @@ -117,7 +117,7 @@ describe('createSearchQuery', () => {
})

expect(query).toContain(
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && (_id match $t1 || _type match $t1 || title match $t1)]',
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && (_id match $t1 || _type match $t1 || title match $t1) && !(_id in path("versions.**"))]',
)
expect(params.t0).toEqual('term0*')
expect(params.t1).toEqual('term1*')
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('createSearchQuery', () => {

const result = [
`// findability-mvi:${FINDABILITY_MVI}\n` +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0)]{_type, _id, _version, object{field}}',
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && !(_id in path("versions.**"))]{_type, _id, _version, object{field}}',
'|order(_id asc)[0...$__limit]',
'{_type, _id, _version, ...select(_type == "basic-schema-test" => { "w0": _id,"w1": _type,"w2": title })}',
].join('')
Expand Down Expand Up @@ -193,7 +193,7 @@ describe('createSearchQuery', () => {
)

expect(query).toContain(
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && (randomCondition == $customParam)]',
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && (randomCondition == $customParam) && !(_id in path("versions.**"))]',
)
expect(params.customParam).toEqual('custom')
})
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('createSearchQuery', () => {

expect(query).toEqual(
`// findability-mvi:${FINDABILITY_MVI}\n` +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0)]' +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && !(_id in path("versions.**"))]' +
'| order(exampleField desc)' +
'[0...$__limit]' +
'{_type, _id, _version, ...select(_type == "basic-schema-test" => { "w0": _id,"w1": _type,"w2": title })}',
Expand Down Expand Up @@ -275,7 +275,7 @@ describe('createSearchQuery', () => {

const result = [
`// findability-mvi:${FINDABILITY_MVI}\n`,
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0)]| ',
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && !(_id in path("versions.**"))]| ',
'order(exampleField desc,anotherExampleField asc,lower(mapWithField) asc)',
'[0...$__limit]{_type, _id, _version, ...select(_type == "basic-schema-test" => { "w0": _id,"w1": _type,"w2": title })}',
].join('')
Expand All @@ -291,7 +291,7 @@ describe('createSearchQuery', () => {

expect(query).toEqual(
`// findability-mvi:${FINDABILITY_MVI}\n` +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0)]' +
'*[_type in $__types && (_id match $t0 || _type match $t0 || title match $t0) && !(_id in path("versions.**"))]' +
'| order(_id asc)' +
'[0...$__limit]' +
'{_type, _id, _version, ...select(_type == "basic-schema-test" => { "w0": _id,"w1": _type,"w2": title })}',
Expand Down Expand Up @@ -403,7 +403,7 @@ describe('createSearchQuery', () => {
* This is an improvement over before, where an illegal term was used (number-as-string, ala ["0"]),
* which lead to no hits at all. */
`// findability-mvi:${FINDABILITY_MVI}\n` +
'*[_type in $__types && (_id match $t0 || _type match $t0 || cover[].cards[].title match $t0) && (_id match $t1 || _type match $t1 || cover[].cards[].title match $t1)]' +
'*[_type in $__types && (_id match $t0 || _type match $t0 || cover[].cards[].title match $t0) && (_id match $t1 || _type match $t1 || cover[].cards[].title match $t1) && !(_id in path("versions.**"))]' +
'| order(_id asc)' +
'[0...$__limit]' +
// at this point we could refilter using cover[0].cards[0].title.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export function createSearchQuery(
...createConstraints(terms, specs),
filter ? `(${filter})` : '',
searchTerms.filter ? `(${searchTerms.filter})` : '',
// Versions are collated server-side using the `bundlePerspective` option. Therefore, they must
// not be fetched individually.
'!(_id in path("versions.**"))',
].filter(Boolean)

const selections = specs.map((spec) => {
Expand Down Expand Up @@ -186,7 +189,11 @@ export function createSearchQuery(
__limit: limit,
...(params || {}),
},
options: {tag, perspective: searchOpts.perspective},
options: {
tag,
perspective: searchOpts.perspective,
bundlePerspective: searchOpts.bundlePerspective,
},
searchSpec: specs,
terms,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {type SanityDocumentLike} from '@sanity/types'
import {Box, type ResponsiveMarginProps, type ResponsivePaddingProps} from '@sanity/ui'
import {type MouseEvent, useCallback, useMemo} from 'react'
import {useIntentLink} from 'sanity/router'
import {useIntentLink, useRouter} from 'sanity/router'

import {type GeneralPreviewLayoutKey, PreviewCard} from '../../../../../../../components'
import {useSchema} from '../../../../../../../hooks'
Expand Down Expand Up @@ -31,7 +31,7 @@ export function SearchResultItem({
const schema = useSchema()
const type = schema.get(documentType)
const documentPresence = useDocumentPresence(documentId)

const perspective = useRouter().stickyParams.perspective
const params = useMemo(() => ({id: documentId, type: type?.name}), [documentId, type?.name])
const {onClick: onIntentClick, href} = useIntentLink({
intent: 'edit',
Expand Down Expand Up @@ -65,6 +65,7 @@ export function SearchResultItem({
<SearchResultItemPreview
documentId={documentId}
layout={layout}
perspective={perspective}
presence={documentPresence}
schemaType={type}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {type SchemaType} from '@sanity/types'
import {Badge, Box, Flex} from '@sanity/ui'
import {useMemo} from 'react'
import {useObservable} from 'react-rx'
import {getPublishedId, getVersionFromId, isVersionId} from 'sanity'
import {getPublishedId} from 'sanity'
import {styled} from 'styled-components'

import {type GeneralPreviewLayoutKey} from '../../../../../../../components'
Expand All @@ -19,6 +19,7 @@ import {type DocumentPresence, useDocumentPreviewStore} from '../../../../../../

interface SearchResultItemPreviewProps {
documentId: string
perspective?: string
layout?: GeneralPreviewLayoutKey
presence?: DocumentPresence[]
schemaType: SchemaType
Expand All @@ -29,7 +30,8 @@ interface SearchResultItemPreviewProps {
* Temporary workaround: force all nested boxes on iOS to use `background-attachment: scroll`
* to allow <Skeleton> components to render correctly within virtual lists.
*/
const SearchResultItemPreviewBox = styled(Box)`
const SearchResultItemPreviewBox = styled(Box)<{$isInPerspective: boolean}>`
opacity: ${(props) => (props.$isInPerspective ? 1 : 0.5)};
@supports (-webkit-overflow-scrolling: touch) {
* [data-ui='Box'] {
background-attachment: scroll;
Expand All @@ -43,6 +45,7 @@ const SearchResultItemPreviewBox = styled(Box)`
export function SearchResultItemPreview({
documentId,
layout,
perspective,
presence,
schemaType,
showBadge = true,
Expand All @@ -56,15 +59,16 @@ export function SearchResultItemPreview({
schemaType,
getPublishedId(documentId),
'',
getVersionFromId(documentId),
perspective?.startsWith('bundle.') ? perspective.split('bundle.').at(1) : undefined,
),
[documentId, documentPreviewStore, schemaType],
[documentId, documentPreviewStore, perspective, schemaType],
)

const {draft, published, isLoading, version} = useObservable(observable, {
draft: null,
isLoading: true,
published: null,
version: null,
})

const sanityDocument = useMemo(() => {
Expand All @@ -80,24 +84,22 @@ export function SearchResultItemPreview({
<Flex align="center" gap={3}>
{presence && presence.length > 0 && <DocumentPreviewPresence presence={presence} />}
{showBadge && <Badge>{schemaType.title}</Badge>}
<DocumentStatusIndicator draft={draft} published={published} />
<DocumentStatusIndicator draft={draft} published={published} version={version} />
</Flex>
)
}, [draft, isLoading, presence, published, schemaType.title, showBadge])
}, [draft, isLoading, presence, published, schemaType.title, showBadge, version])

const tooltip = <DocumentStatus draft={draft} published={published} />
const tooltip = <DocumentStatus draft={draft} published={published} version={version} />

return (
<SearchResultItemPreviewBox>
<SearchResultItemPreviewBox $isInPerspective={!perspective || Boolean(version)}>
<SanityDefaultPreview
{...getPreviewValueWithFallback({
draft,
published,
version,
value: sanityDocument,
perspective: isVersionId(documentId)
? `bundle.${getVersionFromId(documentId)}`
: undefined,
perspective,
})}
isPlaceholder={isLoading ?? true}
layout={layout || 'default'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {isEqual} from 'lodash'
import {type ReactNode, useEffect, useMemo, useReducer, useRef, useState} from 'react'
import {DRAFTS_FOLDER} from 'sanity'
import {SearchContext} from 'sanity/_singletons'
import {useRouter} from 'sanity/router'

import {type CommandListHandle} from '../../../../../../components'
import {useSchema} from '../../../../../../hooks'
Expand Down Expand Up @@ -30,7 +32,7 @@ interface SearchProviderProps {
export function SearchProvider({children, fullscreen}: SearchProviderProps) {
const [onClose, setOnClose] = useState<(() => void) | null>(null)
const [searchCommandList, setSearchCommandList] = useState<CommandListHandle | null>(null)

const perspective = useRouter().stickyParams.perspective
const schema = useSchema()
const currentUser = useCurrentUser()
const {
Expand Down Expand Up @@ -140,6 +142,10 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {
skipSortByScore: ordering.ignoreScore,
...(ordering.sort ? {sort: [ordering.sort]} : {}),
cursor: cursor || undefined,
perspective: omitBundlePerspective(perspective),
bundlePerspective: perspective?.startsWith('bundle.')
? [perspective.split('bundle.').at(1), DRAFTS_FOLDER].join(',')
: undefined,
},
terms: {
...terms,
Expand All @@ -165,6 +171,7 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {
searchState.terms,
terms,
cursor,
perspective,
])

/**
Expand Down Expand Up @@ -197,3 +204,11 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {

return <SearchContext.Provider value={value}>{children}</SearchContext.Provider>
}

function omitBundlePerspective(perspective: string | undefined): string | undefined {
if (perspective?.startsWith('bundle.')) {
return undefined
}

return perspective
}
42 changes: 4 additions & 38 deletions packages/sanity/src/core/util/draftUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,57 +177,23 @@ export interface CollatedHit<T extends {_id: string} = {_id: string}> {
type: string
draft?: T
published?: T
version?: T
}

interface CollateOptions {
bundlePerspective?: string
}

/** @internal */
export function collate<
T extends {
_id: string
_type: string
},
>(documents: T[], {bundlePerspective}: CollateOptions = {}): CollatedHit<T>[] {
export function collate<T extends {_id: string; _type: string}>(documents: T[]): CollatedHit<T>[] {
const byId = documents.reduce((res, doc) => {
const publishedId = getPublishedId(doc._id)
const isVersion = isVersionId(doc._id)
const bundle = getVersionFromId(doc._id)

let entry = res.get(publishedId)
if (!entry) {
entry = {
id: publishedId,
type: doc._type,
published: undefined,
draft: undefined,
version: undefined,
}
entry = {id: publishedId, type: doc._type, published: undefined, draft: undefined}
res.set(publishedId, entry)
}

if (bundlePerspective && bundle === bundlePerspective) {
entry.version = doc
}

if (!isVersion) {
entry[publishedId === doc._id ? 'published' : 'draft'] = doc
}

entry[publishedId === doc._id ? 'published' : 'draft'] = doc
return res
}, new Map())

return (
Array.from(byId.values())
// Remove entries that have no data, because all the following conditions are true:
//
// 1. They have no published version.
// 2. They have no draft version.
// 3. They have a version, but not the one that is currently checked out.
.filter((entry) => entry.published ?? entry.version ?? entry.draft)
)
return Array.from(byId.values())
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
DocumentStatus,
DocumentStatusIndicator,
type GeneralPreviewLayoutKey,
getDocumentIsInPerspective,
getPreviewStateObservable,
getPreviewValueWithFallback,
isRecord,
Expand Down Expand Up @@ -66,13 +65,10 @@ export function PaneItemPreview(props: PaneItemPreviewProps) {
draft: null,
isLoading: true,
published: null,
version: null,
perspective,
})

const isInPerspective = useMemo(
() => getDocumentIsInPerspective(value._id, perspective),
[perspective, value._id],
)

const status = isLoading ? null : (
<TooltipDelayGroupProvider>
<Flex align="center" gap={3}>
Expand All @@ -85,7 +81,7 @@ export function PaneItemPreview(props: PaneItemPreviewProps) {
const tooltip = <DocumentStatus draft={draft} published={published} version={version} />

return (
<Root $isInPerspective={isInPerspective}>
<Root $isInPerspective={!perspective || Boolean(version)}>
<SanityDefaultPreview
{...getPreviewValueWithFallback({value, draft, published, version, perspective})}
isPlaceholder={isLoading}
Expand Down
Loading

0 comments on commit da3f744

Please sign in to comment.