From 0ede4cf3f877d49400e1ca75c237c8897e2888a0 Mon Sep 17 00:00:00 2001 From: Ash Date: Thu, 2 May 2024 14:00:14 +0100 Subject: [PATCH] fix(sanity): do not order by `_updatedAt` when relevance ordering is used with Text Search API search strategy (#6537) * fix(sanity): do not order by `_updatedAt` when relevance ordering is used with Text Search API search strategy * fix(sanity): findability comment * test(sanity): ensure ordering is applied correctly --- .../search/text-search/createTextSearch.ts | 2 +- .../navbar/search/components/SortMenu.tsx | 31 ++++++---- .../search/contexts/search/SearchProvider.tsx | 26 +++++--- .../search/contexts/search/reducer.test.ts | 60 +++++++++++++++++-- .../navbar/search/contexts/search/reducer.ts | 10 +++- .../{orderings.ts => getOrderings.ts} | 8 ++- .../studio/components/navbar/search/types.ts | 2 +- 7 files changed, 106 insertions(+), 33 deletions(-) rename packages/sanity/src/core/studio/components/navbar/search/definitions/{orderings.ts => getOrderings.ts} (77%) diff --git a/packages/sanity/src/core/search/text-search/createTextSearch.ts b/packages/sanity/src/core/search/text-search/createTextSearch.ts index 15a4eb60b2f..1afbd3a20c2 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -155,7 +155,7 @@ export const createTextSearch: SearchStrategyFactory = ( ...searchTerms.params, }, types: getDocumentTypeConfiguration(searchOptions, searchTerms), - order: getOrder(searchOptions.sort), + ...(searchOptions.sort ? {order: getOrder(searchOptions.sort)} : {}), includeAttributes: ['_id', '_type'], fromCursor: searchOptions.cursor, limit: searchOptions.limit ?? DEFAULT_LIMIT, diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx index 4632901177d..4fd2badd58e 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx @@ -2,28 +2,19 @@ import {SortIcon} from '@sanity/icons' import {Card, Flex, Menu, MenuDivider} from '@sanity/ui' import {isEqual} from 'lodash' import {useCallback, useId, useMemo} from 'react' +import {useWorkspace} from 'sanity' import {styled} from 'styled-components' import {Button, MenuButton, MenuItem} from '../../../../../../ui-components' import {useTranslation} from '../../../../../i18n' import {useSearchState} from '../contexts/search/useSearchState' -import {ORDERINGS} from '../definitions/orderings' +import {getOrderings} from '../definitions/getOrderings' import {type SearchOrdering} from '../types' interface SearchDivider { type: 'divider' } -const MENU_ORDERINGS: (SearchDivider | SearchOrdering)[] = [ - ORDERINGS.relevance, - {type: 'divider'}, - ORDERINGS.createdAsc, - ORDERINGS.createdDesc, - {type: 'divider'}, - ORDERINGS.updatedAsc, - ORDERINGS.updatedDesc, -] - const SortMenuContentFlex = styled(Flex)` box-sizing: border-box; ` @@ -57,13 +48,27 @@ function CustomMenuItem({ordering}: {ordering: SearchOrdering}) { export function SortMenu() { const {t} = useTranslation() + const {enableLegacySearch = false} = useWorkspace().search const { state: {ordering}, } = useSearchState() const menuButtonId = useId() - const currentMenuItem = MENU_ORDERINGS.find( + const menuOrderings: (SearchDivider | SearchOrdering)[] = useMemo(() => { + const orderings = getOrderings({enableLegacySearch}) + return [ + orderings.relevance, + {type: 'divider'}, + orderings.createdAsc, + orderings.createdDesc, + {type: 'divider'}, + orderings.updatedAsc, + orderings.updatedDesc, + ] + }, [enableLegacySearch]) + + const currentMenuItem = menuOrderings.find( (item): item is SearchOrdering => isEqual(ordering, item) && !isSearchDivider(item), ) @@ -79,7 +84,7 @@ export function SortMenu() { id={menuButtonId || ''} menu={ - {MENU_ORDERINGS.map((item, index) => { + {menuOrderings.map((item, index) => { if (isSearchDivider(item)) { // eslint-disable-next-line react/no-array-index-key return diff --git a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/SearchProvider.tsx b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/SearchProvider.tsx index d1fbe64708e..4a4082f9299 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/SearchProvider.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/SearchProvider.tsx @@ -34,7 +34,7 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) { const schema = useSchema() const currentUser = useCurrentUser() const { - search: {operators, filters}, + search: {operators, filters, enableLegacySearch}, } = useSource() // Create field, filter and operator dictionaries @@ -60,8 +60,16 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) { cursor: null, nextCursor: null, }, + enableLegacySearch, }), - [currentUser, fieldDefinitions, filterDefinitions, fullscreen, operatorDefinitions], + [ + currentUser, + fieldDefinitions, + filterDefinitions, + fullscreen, + operatorDefinitions, + enableLegacySearch, + ], ) const [state, dispatch] = useReducer(searchReducer, initialState) @@ -112,9 +120,13 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) { const termsChanged = !isEqual(terms, previousTermsRef.current) if (orderingChanged || cursorChanged || termsChanged) { - // Use a custom label if provided, otherwise return field and direction, e.g. `_updatedAt desc` - const sortLabel = - ordering?.customMeasurementLabel || `${ordering.sort.field} ${ordering.sort.direction}` + let sortLabel = 'findability-sort:' + + if (ordering?.customMeasurementLabel || ordering.sort) { + // Use a custom label if provided, otherwise return field and direction, e.g. `_updatedAt desc` + sortLabel += + ordering?.customMeasurementLabel || `${ordering.sort?.field} ${ordering.sort?.direction}` + } handleSearch({ options: { @@ -124,13 +136,13 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) { ? [`findability-recent-search:${terms.__recent.index}`] : []), `findability-selected-types:${terms.types.length}`, - `findability-sort:${sortLabel}`, + sortLabel, `findability-source: global`, `findability-filter-count:${completeFilters.length}`, ], limit: SEARCH_LIMIT, skipSortByScore: ordering.ignoreScore, - sort: [ordering.sort], + ...(ordering.sort ? {sort: [ordering.sort]} : {}), cursor: cursor || undefined, }, terms: { diff --git a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.test.ts b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.test.ts index 72246f16342..af92a43dadb 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.test.ts +++ b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.test.ts @@ -6,6 +6,7 @@ import {useReducer} from 'react' import {type RecentSearch} from '../../datastores/recentSearches' import {type SearchOrdering} from '../../types' import { + type InitialSearchState, initialSearchState, type SearchAction, searchReducer, @@ -39,12 +40,15 @@ const recentSearchTerms = { query: 'foo', types: [], } as RecentSearch + +const initialStateContext: InitialSearchState = { + currentUser: mockUser, + definitions: {fields: {}, filters: {}, operators: {}}, + pagination: {cursor: null, nextCursor: null}, +} + const initialState: SearchReducerState = { - ...initialSearchState({ - currentUser: mockUser, - definitions: {fields: {}, filters: {}, operators: {}}, - pagination: {cursor: null, nextCursor: null}, - }), + ...initialSearchState(initialStateContext), terms: recentSearchTerms, } @@ -119,6 +123,52 @@ describe('searchReducer', () => { expect((state.terms as RecentSearch).__recent).toBeUndefined() }) + it('should not include an order when using Text Search API strategy and ordering by relevance', () => { + const {result} = renderHook(() => + useReducer( + searchReducer, + initialSearchState({ + ...initialStateContext, + enableLegacySearch: false, + }), + ), + ) + + const [state] = result.current + + expect(state.ordering).toMatchInlineSnapshot(` +Object { + "customMeasurementLabel": "relevance", + "titleKey": "search.ordering.best-match-label", +} +`) + }) + + it('should order by `_updatedAt` when using GROQ Query API strategy and ordering by relevance', () => { + const {result} = renderHook(() => + useReducer( + searchReducer, + initialSearchState({ + ...initialStateContext, + enableLegacySearch: true, + }), + ), + ) + + const [state] = result.current + + expect(state.ordering).toMatchInlineSnapshot(` +Object { + "customMeasurementLabel": "relevance", + "sort": Object { + "direction": "desc", + "field": "_updatedAt", + }, + "titleKey": "search.ordering.best-match-label", +} +`) + }) + it('should merge results after fetching an additional page', () => { const {result} = renderHook(() => useReducer(searchReducer, initialState)) const [, dispatch] = result.current diff --git a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.ts b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.ts index 1988dfaee02..6b23165f8b7 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.ts +++ b/packages/sanity/src/core/studio/components/navbar/search/contexts/search/reducer.ts @@ -5,12 +5,12 @@ import {getPublishedId} from '../../../../../../util' import {type RecentSearch} from '../../datastores/recentSearches' import {type SearchFieldDefinitionDictionary} from '../../definitions/fields' import {type SearchFilterDefinitionDictionary} from '../../definitions/filters' +import {getOrderings} from '../../definitions/getOrderings' import { getOperatorDefinition, getOperatorInitialValue, type SearchOperatorDefinitionDictionary, } from '../../definitions/operators' -import {ORDERINGS} from '../../definitions/orderings' import {type SearchFilter, type SearchOrdering} from '../../types' import {debugWithName, isDebugMode} from '../../utils/debug' import { @@ -40,6 +40,7 @@ export type SearchReducerState = PaginationState & { ordering: SearchOrdering result: SearchResult terms: RecentSearch | SearchTerms + enableLegacySearch?: boolean } export interface SearchDefinitions { @@ -61,6 +62,7 @@ export interface InitialSearchState { fullscreen?: boolean definitions: SearchDefinitions pagination: PaginationState + enableLegacySearch?: boolean } export function initialSearchState({ @@ -68,6 +70,7 @@ export function initialSearchState({ fullscreen, definitions, pagination, + enableLegacySearch, }: InitialSearchState): SearchReducerState { return { currentUser, @@ -77,7 +80,7 @@ export function initialSearchState({ filtersVisible: true, fullscreen, lastActiveIndex: -1, - ordering: ORDERINGS.relevance, + ordering: getOrderings({enableLegacySearch}).relevance, ...pagination, result: { error: null, @@ -91,6 +94,7 @@ export function initialSearchState({ types: [], }, definitions, + enableLegacySearch, } } @@ -173,7 +177,7 @@ export function searchReducer(state: SearchReducerState, action: SearchAction): case 'ORDERING_RESET': return { ...state, - ordering: ORDERINGS.relevance, + ordering: getOrderings({enableLegacySearch: state.enableLegacySearch}).relevance, terms: stripRecent(state.terms), result: { ...state.result, diff --git a/packages/sanity/src/core/studio/components/navbar/search/definitions/orderings.ts b/packages/sanity/src/core/studio/components/navbar/search/definitions/getOrderings.ts similarity index 77% rename from packages/sanity/src/core/studio/components/navbar/search/definitions/orderings.ts rename to packages/sanity/src/core/studio/components/navbar/search/definitions/getOrderings.ts index fcf9bbadef4..3cd6ae4cec6 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/definitions/orderings.ts +++ b/packages/sanity/src/core/studio/components/navbar/search/definitions/getOrderings.ts @@ -1,6 +1,8 @@ import {type SearchOrdering} from '../types' -export const ORDERINGS: Record = { +export const getOrderings: (context: { + enableLegacySearch?: boolean +}) => Record = ({enableLegacySearch}) => ({ createdAsc: { ignoreScore: true, sort: {direction: 'asc', field: '_createdAt'}, @@ -13,7 +15,7 @@ export const ORDERINGS: Record = { }, relevance: { customMeasurementLabel: 'relevance', - sort: {direction: 'desc', field: '_updatedAt'}, + ...(enableLegacySearch ? {sort: {direction: 'desc', field: '_updatedAt'}} : {}), titleKey: 'search.ordering.best-match-label', }, updatedAsc: { @@ -26,4 +28,4 @@ export const ORDERINGS: Record = { sort: {direction: 'desc', field: '_updatedAt'}, titleKey: 'search.ordering.updated-descending-label', }, -} +}) diff --git a/packages/sanity/src/core/studio/components/navbar/search/types.ts b/packages/sanity/src/core/studio/components/navbar/search/types.ts index 543ed71d250..b5309a4217c 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/types.ts +++ b/packages/sanity/src/core/studio/components/navbar/search/types.ts @@ -72,7 +72,7 @@ export type SearchFilterValues = { export interface SearchOrdering { customMeasurementLabel?: string ignoreScore?: boolean - sort: SearchSort + sort?: SearchSort /** * i18n key for title */