From 02edd32c41bd06e995838d2f4a2d978629128e89 Mon Sep 17 00:00:00 2001 From: Pedro Soares Date: Thu, 14 Nov 2024 16:02:20 -0300 Subject: [PATCH 1/8] feat: display product gallery without blink --- packages/api/src/__generated__/schema.ts | 2 + .../platforms/vtex/clients/search/index.ts | 49 +++++++++++++++++-- .../platforms/vtex/resolvers/searchResult.ts | 7 +-- .../api/src/platforms/vtex/utils/facets.ts | 10 ++++ packages/api/src/typeDefs/query.graphql | 4 ++ packages/core/@generated/gql.ts | 4 +- packages/core/@generated/graphql.ts | 20 ++++++-- .../templates/SearchPage/SearchWrapper.tsx | 13 ++++- .../src/sdk/product/useProductGalleryQuery.ts | 26 ++++++++++ packages/core/src/sdk/search/state.ts | 7 ++- 10 files changed, 123 insertions(+), 19 deletions(-) diff --git a/packages/api/src/__generated__/schema.ts b/packages/api/src/__generated__/schema.ts index d65444fefd..49ec2eac20 100644 --- a/packages/api/src/__generated__/schema.ts +++ b/packages/api/src/__generated__/schema.ts @@ -530,6 +530,8 @@ export type QueryShippingArgs = { /** Search result. */ export type SearchMetadata = { __typename?: 'SearchMetadata'; + /** Indicates how the search engine corrected the misspelled word by using fuzzy logic. */ + fuzzy?: Maybe; /** Indicates if the search term was misspelled. */ isTermMisspelled: Scalars['Boolean']; /** Logical operator used to run the search. */ diff --git a/packages/api/src/platforms/vtex/clients/search/index.ts b/packages/api/src/platforms/vtex/clients/search/index.ts index a64a383c82..c832d3e132 100644 --- a/packages/api/src/platforms/vtex/clients/search/index.ts +++ b/packages/api/src/platforms/vtex/clients/search/index.ts @@ -1,7 +1,7 @@ import type { Context, Options } from '../../' import type { IStoreSelectedFacet } from '../../../../__generated__/schema' import { getStoreCookie } from '../../utils/cookies' -import type { SelectedFacet } from '../../utils/facets' +import type { FuzzyFacet, OperatorFacet, SelectedFacet } from '../../utils/facets' import { fetchAPI } from '../fetch' import type { Facet, @@ -43,12 +43,33 @@ export interface ProductLocator { const POLICY_KEY = 'trade-policy' const REGION_KEY = 'region-id' -const CHANNEL_KEYS = new Set([POLICY_KEY, REGION_KEY]) +const FUZZY_KEY = 'fuzzy' +const OPERATOR_KEY = 'operator' + +const EXTRA_FACETS_KEYS = new Set([ + POLICY_KEY, + REGION_KEY, + FUZZY_KEY, + OPERATOR_KEY, +]) export const isFacetBoolean = ( facet: Facet ): facet is Facet => facet.type === 'TEXT' +const isFuzzyFacet = (facet: SelectedFacet): facet is FuzzyFacet => { + return ( + facet.key === 'fuzzy' && + (facet.value === '0' || facet.value === '1' || facet.value === 'auto') + ) +} + +const isOperatorFacet = (facet: SelectedFacet): facet is OperatorFacet => { + return ( + facet.key === 'operator' && (facet.value === 'and' || facet.value === 'or') + ) +} + export const IntelligentSearch = ( { account, environment, hideUnavailableItems, simulationBehavior, showSponsored }: Options, ctx: Context @@ -87,7 +108,9 @@ export const IntelligentSearch = ( } const addDefaultFacets = (facets: SelectedFacet[]) => { - const withDefaultFacets = facets.filter(({ key }) => !CHANNEL_KEYS.has(key)) + const withDefaultFacets = facets.filter( + ({ key }) => !EXTRA_FACETS_KEYS.has(key) + ) const policyFacet = facets.find(({ key }) => key === POLICY_KEY) ?? getPolicyFacet() @@ -106,6 +129,22 @@ export const IntelligentSearch = ( return withDefaultFacets } + const addSearchParamsFacets = ( + facets: SelectedFacet[], + params: URLSearchParams + ) => { + const fuzzyFacet = facets.find(({ key }) => key === FUZZY_KEY) ?? null + const operatorFacet = facets.find(({ key }) => key === OPERATOR_KEY) ?? null + + if (fuzzyFacet && isFuzzyFacet(fuzzyFacet)) { + params.append(FUZZY_KEY, fuzzyFacet.value) + } + + if (operatorFacet && isOperatorFacet(operatorFacet)) { + params.append(OPERATOR_KEY, operatorFacet.value) + } + } + const search = ({ query = '', page, @@ -113,7 +152,6 @@ export const IntelligentSearch = ( sort = '', selectedFacets = [], type, - fuzzy = 'auto', showInvisibleItems, }: SearchArgs): Promise => { const params = new URLSearchParams({ @@ -121,10 +159,11 @@ export const IntelligentSearch = ( count: count.toString(), query, sort, - fuzzy, locale: ctx.storage.locale, }) + addSearchParamsFacets(selectedFacets, params) + if (showInvisibleItems) { params.append('show-invisible-items', 'true') } diff --git a/packages/api/src/platforms/vtex/resolvers/searchResult.ts b/packages/api/src/platforms/vtex/resolvers/searchResult.ts index e1e47131c5..1cba606a8b 100644 --- a/packages/api/src/platforms/vtex/resolvers/searchResult.ts +++ b/packages/api/src/platforms/vtex/resolvers/searchResult.ts @@ -117,16 +117,13 @@ export const StoreSearchResult: Record> = { return filteredFacets }, - metadata: async ({ searchArgs, productSearchPromise }) => { - if (!searchArgs.query) { - return null - } - + metadata: async ({ productSearchPromise }) => { const productSearchResult = await productSearchPromise return { isTermMisspelled: productSearchResult.correction?.misspelled ?? false, logicalOperator: productSearchResult.operator, + fuzzy: productSearchResult.fuzzy } }, } diff --git a/packages/api/src/platforms/vtex/utils/facets.ts b/packages/api/src/platforms/vtex/utils/facets.ts index 2291f5c2f2..c86d0faccf 100644 --- a/packages/api/src/platforms/vtex/utils/facets.ts +++ b/packages/api/src/platforms/vtex/utils/facets.ts @@ -20,6 +20,16 @@ export const FACET_CROSS_SELLING_MAP = { suggestions: "suggestions", } as const +export type FuzzyFacet = { + key: 'fuzzy' + value: '0' | '1' | 'auto' +} + +export type OperatorFacet = { + key: 'operator' + value: 'and' | 'or' +} + /** * Transform facets from the store to VTEX platform facets. * For instance, the channel in Store becomes trade-policy and regionId in VTEX's realm diff --git a/packages/api/src/typeDefs/query.graphql b/packages/api/src/typeDefs/query.graphql index 0e38c291b2..e00b81fd95 100644 --- a/packages/api/src/typeDefs/query.graphql +++ b/packages/api/src/typeDefs/query.graphql @@ -160,6 +160,10 @@ type SearchMetadata { Logical operator used to run the search. """ logicalOperator: String! + """ + Indicates how the search engine corrected the misspelled word by using fuzzy logic. + """ + fuzzy: String } """ diff --git a/packages/core/@generated/gql.ts b/packages/core/@generated/gql.ts index f499ff8e2c..9976f8ef8c 100644 --- a/packages/core/@generated/gql.ts +++ b/packages/core/@generated/gql.ts @@ -44,7 +44,7 @@ const documents = { types.SubscribeToNewsletterDocument, '\n query ClientManyProductsQuery(\n $first: Int!\n $after: String\n $sort: StoreSort!\n $term: String!\n $selectedFacets: [IStoreSelectedFacet!]!\n ) {\n ...ClientManyProducts\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n edges {\n node {\n ...ProductSummary_product\n }\n }\n }\n }\n }\n': types.ClientManyProductsQueryDocument, - '\n query ClientProductGalleryQuery(\n $first: Int!\n $after: String!\n $sort: StoreSort!\n $term: String!\n $selectedFacets: [IStoreSelectedFacet!]!\n ) {\n ...ClientProductGallery\n redirect(term: $term, selectedFacets: $selectedFacets) {\n url\n }\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n }\n facets {\n ...Filter_facets\n }\n metadata {\n ...SearchEvent_metadata\n }\n }\n }\n\n fragment SearchEvent_metadata on SearchMetadata {\n isTermMisspelled\n logicalOperator\n }\n': + '\n query ClientProductGalleryQuery(\n $first: Int!\n $after: String!\n $sort: StoreSort!\n $term: String!\n $selectedFacets: [IStoreSelectedFacet!]!\n ) {\n ...ClientProductGallery\n redirect(term: $term, selectedFacets: $selectedFacets) {\n url\n }\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n }\n facets {\n ...Filter_facets\n }\n metadata {\n ...SearchEvent_metadata\n }\n }\n }\n\n fragment SearchEvent_metadata on SearchMetadata {\n isTermMisspelled\n logicalOperator\n fuzzy\n }\n': types.ClientProductGalleryQueryDocument, '\n query ClientProductQuery($locator: [IStoreSelectedFacet!]!) {\n ...ClientProduct\n product(locator: $locator) {\n ...ProductDetailsFragment_product\n }\n }\n': types.ClientProductQueryDocument, @@ -158,7 +158,7 @@ export function gql( * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql( - source: '\n query ClientProductGalleryQuery(\n $first: Int!\n $after: String!\n $sort: StoreSort!\n $term: String!\n $selectedFacets: [IStoreSelectedFacet!]!\n ) {\n ...ClientProductGallery\n redirect(term: $term, selectedFacets: $selectedFacets) {\n url\n }\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n }\n facets {\n ...Filter_facets\n }\n metadata {\n ...SearchEvent_metadata\n }\n }\n }\n\n fragment SearchEvent_metadata on SearchMetadata {\n isTermMisspelled\n logicalOperator\n }\n' + source: '\n query ClientProductGalleryQuery(\n $first: Int!\n $after: String!\n $sort: StoreSort!\n $term: String!\n $selectedFacets: [IStoreSelectedFacet!]!\n ) {\n ...ClientProductGallery\n redirect(term: $term, selectedFacets: $selectedFacets) {\n url\n }\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n }\n facets {\n ...Filter_facets\n }\n metadata {\n ...SearchEvent_metadata\n }\n }\n }\n\n fragment SearchEvent_metadata on SearchMetadata {\n isTermMisspelled\n logicalOperator\n fuzzy\n }\n' ): typeof import('./graphql').ClientProductGalleryQueryDocument /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. diff --git a/packages/core/@generated/graphql.ts b/packages/core/@generated/graphql.ts index b2c736af6e..52ff096a4f 100644 --- a/packages/core/@generated/graphql.ts +++ b/packages/core/@generated/graphql.ts @@ -523,6 +523,8 @@ export type QueryShippingArgs = { /** Search result. */ export type SearchMetadata = { + /** Indicates how the search engine corrected the misspelled word by using fuzzy logic. */ + fuzzy: Maybe /** Indicates if the search term was misspelled. */ isTermMisspelled: Scalars['Boolean']['output'] /** Logical operator used to run the search. */ @@ -1504,13 +1506,18 @@ export type ClientProductGalleryQueryQuery = { max: { selected: number; absolute: number } } > - metadata: { isTermMisspelled: boolean; logicalOperator: string } | null + metadata: { + isTermMisspelled: boolean + logicalOperator: string + fuzzy: string | null + } | null } } export type SearchEvent_MetadataFragment = { isTermMisspelled: boolean logicalOperator: string + fuzzy: string | null } export type ClientProductQueryQueryVariables = Exact<{ @@ -1597,7 +1604,11 @@ export type ClientSearchSuggestionsQueryQuery = { }> } products: { pageInfo: { totalCount: number } } - metadata: { isTermMisspelled: boolean; logicalOperator: string } | null + metadata: { + isTermMisspelled: boolean + logicalOperator: string + fuzzy: string | null + } | null } } @@ -2047,6 +2058,7 @@ export const SearchEvent_MetadataFragmentDoc = new TypedDocumentString( fragment SearchEvent_metadata on SearchMetadata { isTermMisspelled logicalOperator + fuzzy } `, { fragmentName: 'SearchEvent_metadata' } @@ -2099,7 +2111,7 @@ export const ClientManyProductsQueryDocument = { export const ClientProductGalleryQueryDocument = { __meta__: { operationName: 'ClientProductGalleryQuery', - operationHash: '177fe68cb385737b0901fc9e105f0a4813e18a20', + operationHash: 'bfc40da32b60f9404a4adb96b0856e3fbb04b076', }, } as unknown as TypedDocumentString< ClientProductGalleryQueryQuery, @@ -2117,7 +2129,7 @@ export const ClientProductQueryDocument = { export const ClientSearchSuggestionsQueryDocument = { __meta__: { operationName: 'ClientSearchSuggestionsQuery', - operationHash: '4d9f934764d8578aea08673b8ba57e8bf738f534', + operationHash: '47e48eaee91d16a4237eb2c1241bc2ed3e2ad9bb', }, } as unknown as TypedDocumentString< ClientSearchSuggestionsQueryQuery, diff --git a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx index 1d644f8807..3d6b8b830a 100644 --- a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx +++ b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx @@ -3,7 +3,7 @@ import { useSearch } from '@faststore/sdk' import type { SearchContentType } from 'src/server/cms' import type { SearchPageContextType } from 'src/pages/s' -import { useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' +import { findFacetValue, useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' import Section from 'src/components/sections/Section' import EmptyState from 'src/components/sections/EmptyState' import ProductGalleryStyles from 'src/components/sections/ProductGallery/section.module.scss' @@ -28,6 +28,7 @@ export type SearchWrapperProps = { serverData: SearchPageContextType } + export default function SearchWrapper({ itemsPerPage, searchContentType, @@ -37,6 +38,8 @@ export default function SearchWrapper({ const { state: { term, sort, selectedFacets }, } = useSearch() + + const { data: pageProductGalleryData, isValidating } = useProductGalleryQuery( { term, @@ -45,11 +48,17 @@ export default function SearchWrapper({ selectedFacets, } ) - + if (isValidating || !pageProductGalleryData) { return } + const hasFuzzy = findFacetValue(selectedFacets, 'fuzzy') + + if(!hasFuzzy) { + return + } + // Redirect when there are registered Intelligent Search redirects on VTEX Admin if (pageProductGalleryData?.redirect?.url) { router.replace(pageProductGalleryData?.redirect?.url, null, { diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index 40e63948f5..1762e5a2b8 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -10,6 +10,7 @@ import type { ClientProductGalleryQueryQueryVariables as Variables, } from '@generated/graphql' import type { IntelligentSearchQueryEvent } from 'src/sdk/analytics/types' +import { useSearch } from '@faststore/sdk' /** * This query is run on the browser and contains @@ -53,6 +54,7 @@ export const query = gql(` fragment SearchEvent_metadata on SearchMetadata { isTermMisspelled logicalOperator + fuzzy } `) @@ -63,6 +65,14 @@ type ProductGalleryQueryOptions = { term: ClientManyProductsQueryQueryVariables['term'] } +export const findFacetValue = ( + facets: Facet[], + searchParam: string +): string | null => { + const facet = facets.find(({ key }) => key === searchParam) + return facet?.value ?? null +} + export const useProductGalleryQuery = ({ term, sort, @@ -70,6 +80,7 @@ export const useProductGalleryQuery = ({ itemsPerPage, }: ProductGalleryQueryOptions) => { const { locale } = useSession() + const { state, setState } = useSearch() const localizedVariables = useLocalizedVariables({ first: itemsPerPage, after: '0', @@ -80,6 +91,21 @@ export const useProductGalleryQuery = ({ return useQuery(query, localizedVariables, { onSuccess: (data) => { + if (data) { + const fuzzyFacetValue = findFacetValue(selectedFacets, 'fuzzy') + const operatorFacetValue = findFacetValue(selectedFacets, 'operator') + + if (!fuzzyFacetValue && !operatorFacetValue) { + setState({ + ...state, + selectedFacets: [ + ...selectedFacets, + { key: 'fuzzy', value: data.search.metadata?.fuzzy }, + { key: 'operator', value: data.search.metadata?.logicalOperator }, + ], + }) + } + } if (data && term) { import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { sendAnalyticsEvent({ diff --git a/packages/core/src/sdk/search/state.ts b/packages/core/src/sdk/search/state.ts index 01080c1ee9..cbae36b491 100644 --- a/packages/core/src/sdk/search/state.ts +++ b/packages/core/src/sdk/search/state.ts @@ -5,7 +5,12 @@ export const useApplySearchState = () => { const router = useRouter() return useCallback( - (url: URL) => router.push(`${url.pathname}${url.search}`), + (url: URL) => { + const newUrl = `${url.pathname}${url.search}` + return url.searchParams.has('fuzzy') && url.searchParams.has('operator') + ? router.replace(newUrl) + : router.push(newUrl) + }, [router] ) } From 006915f0440341e79117cfd4b5d973a19c8bbefc Mon Sep 17 00:00:00 2001 From: Pedro Soares Date: Fri, 15 Nov 2024 10:02:58 -0300 Subject: [PATCH 2/8] feat: add logic to the hook --- .../templates/SearchPage/SearchWrapper.tsx | 7 +- .../src/sdk/product/useProductGalleryQuery.ts | 76 +++++++++++-------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx index 3d6b8b830a..2930b2ddb9 100644 --- a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx +++ b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx @@ -3,7 +3,7 @@ import { useSearch } from '@faststore/sdk' import type { SearchContentType } from 'src/server/cms' import type { SearchPageContextType } from 'src/pages/s' -import { findFacetValue, useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' +import { useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' import Section from 'src/components/sections/Section' import EmptyState from 'src/components/sections/EmptyState' import ProductGalleryStyles from 'src/components/sections/ProductGallery/section.module.scss' @@ -53,11 +53,6 @@ export default function SearchWrapper({ return } - const hasFuzzy = findFacetValue(selectedFacets, 'fuzzy') - - if(!hasFuzzy) { - return - } // Redirect when there are registered Intelligent Search redirects on VTEX Admin if (pageProductGalleryData?.redirect?.url) { diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index 1762e5a2b8..529b4cbfb5 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -89,38 +89,50 @@ export const useProductGalleryQuery = ({ selectedFacets, }) - return useQuery(query, localizedVariables, { - onSuccess: (data) => { - if (data) { - const fuzzyFacetValue = findFacetValue(selectedFacets, 'fuzzy') - const operatorFacetValue = findFacetValue(selectedFacets, 'operator') - - if (!fuzzyFacetValue && !operatorFacetValue) { - setState({ - ...state, - selectedFacets: [ - ...selectedFacets, - { key: 'fuzzy', value: data.search.metadata?.fuzzy }, - { key: 'operator', value: data.search.metadata?.logicalOperator }, - ], + const fuzzyFacetValue = findFacetValue(selectedFacets, 'fuzzy') + const operatorFacetValue = findFacetValue(selectedFacets, 'operator') + + const queryResult = useQuery( + query, + localizedVariables, + { + onSuccess: (data) => { + if (data && term) { + import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { + sendAnalyticsEvent({ + name: 'intelligent_search_query', + params: { + locale, + term, + url: window.location.href, + logicalOperator: data.search.metadata?.logicalOperator ?? 'and', + isTermMisspelled: + data.search.metadata?.isTermMisspelled ?? false, + totalCount: data.search.products.pageInfo.totalCount, + }, + }) }) } - } - if (data && term) { - import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { - sendAnalyticsEvent({ - name: 'intelligent_search_query', - params: { - locale, - term, - url: window.location.href, - logicalOperator: data.search.metadata?.logicalOperator ?? 'and', - isTermMisspelled: data.search.metadata?.isTermMisspelled ?? false, - totalCount: data.search.products.pageInfo.totalCount, - }, - }) - }) - } - }, - }) + }, + } + ) + + // If there is no fuzzy or operator facet, we need to add them to the selectedFacets and re-fetch the query + if (!fuzzyFacetValue && !operatorFacetValue) { + if (queryResult.data) { + setState({ + ...state, + selectedFacets: [ + ...selectedFacets, + { key: 'fuzzy', value: queryResult.data.search.metadata?.fuzzy }, + { key: 'operator', value: queryResult.data.search.metadata?.logicalOperator }, + ], + }) + } + + // The first result is not relevant, so we return data null to avoid rendering the page while the query is being re-fetched + return { ...queryResult, data: null } + } + + return queryResult } From 5c44306634e08813ec1ee1ed4b322cb9ef3727be Mon Sep 17 00:00:00 2001 From: Pedro Soares Date: Mon, 18 Nov 2024 09:51:29 -0300 Subject: [PATCH 3/8] feat: move loading logic to useProductGalleryQuery hook --- .../src/sdk/product/useProductGalleryQuery.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index 529b4cbfb5..f32c01cc3a 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -92,46 +92,46 @@ export const useProductGalleryQuery = ({ const fuzzyFacetValue = findFacetValue(selectedFacets, 'fuzzy') const operatorFacetValue = findFacetValue(selectedFacets, 'operator') - const queryResult = useQuery( - query, - localizedVariables, - { - onSuccess: (data) => { - if (data && term) { - import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { - sendAnalyticsEvent({ - name: 'intelligent_search_query', - params: { - locale, - term, - url: window.location.href, - logicalOperator: data.search.metadata?.logicalOperator ?? 'and', - isTermMisspelled: - data.search.metadata?.isTermMisspelled ?? false, - totalCount: data.search.products.pageInfo.totalCount, - }, - }) + const queryResult = useQuery(query, localizedVariables, { + onSuccess: (data) => { + if (data && term) { + import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { + sendAnalyticsEvent({ + name: 'intelligent_search_query', + params: { + locale, + term, + url: window.location.href, + logicalOperator: data.search.metadata?.logicalOperator ?? 'and', + isTermMisspelled: data.search.metadata?.isTermMisspelled ?? false, + totalCount: data.search.products.pageInfo.totalCount, + }, }) - } - }, - } - ) + }) + } + }, + }) // If there is no fuzzy or operator facet, we need to add them to the selectedFacets and re-fetch the query - if (!fuzzyFacetValue && !operatorFacetValue) { + const shouldRefetchQuery = + !queryResult.error && (!fuzzyFacetValue || !operatorFacetValue) + if (shouldRefetchQuery) { if (queryResult.data) { setState({ ...state, selectedFacets: [ ...selectedFacets, { key: 'fuzzy', value: queryResult.data.search.metadata?.fuzzy }, - { key: 'operator', value: queryResult.data.search.metadata?.logicalOperator }, + { + key: 'operator', + value: queryResult.data.search.metadata?.logicalOperator, + }, ], }) } // The first result is not relevant, so we return data null to avoid rendering the page while the query is being re-fetched - return { ...queryResult, data: null } + return { ...queryResult, isValidating: true, data: null } } return queryResult From ca54fc4d3a32cbf40d7d0604aa2177dd3bc7df1a Mon Sep 17 00:00:00 2001 From: Fanny Chien Date: Thu, 14 Nov 2024 19:36:35 -0300 Subject: [PATCH 4/8] chore: updates api mocks and test --- packages/api/mocks/AllProductsQuery.ts | 22 +++++++++++----------- packages/api/mocks/ProductQuery.ts | 6 +++--- packages/api/mocks/RedirectQuery.ts | 2 +- packages/api/mocks/SearchQuery.ts | 18 +++++++++--------- packages/api/mocks/ValidateCartMutation.ts | 4 ++-- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/api/mocks/AllProductsQuery.ts b/packages/api/mocks/AllProductsQuery.ts index 2ad640de6f..0db66f601c 100644 --- a/packages/api/mocks/AllProductsQuery.ts +++ b/packages/api/mocks/AllProductsQuery.ts @@ -78,7 +78,7 @@ export const AllProductsQueryFirst5 = `query AllProducts { ` export const productSearchPage1Count5Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { @@ -86,8 +86,8 @@ export const productSearchPage1Count5Fetch = { { cacheId: 'sp-99995946', productId: '99995946', - description: '4k Philips Monitor 27\"', - productName: '4k Philips Monitor 27\"', + description: '4k Philips Monitor 27"', + productName: '4k Philips Monitor 27"', linkText: '4k-philips-monitor', brand: 'adidas', brandId: 2000004, @@ -209,7 +209,7 @@ export const productSearchPage1Count5Fetch = { ], itemId: '99988213', name: 'Monitor 27', - nameComplete: '4k Philips Monitor 27\" Monitor 27', + nameComplete: '4k Philips Monitor 27" Monitor 27', complementName: '', referenceId: [ { @@ -831,36 +831,36 @@ export const productSearchPage1Count5Fetch = { current: { index: 1, proxyUrl: - 'search/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=0&operator=and', }, before: [], after: [ { index: 2, proxyUrl: - 'search/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=0&operator=and', }, { index: 3, proxyUrl: - 'search/trade-policy/1?page=3&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=3&count=5&query=&sort=&fuzzy=0&operator=and', }, { index: 4, proxyUrl: - 'search/trade-policy/1?page=4&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=4&count=5&query=&sort=&fuzzy=0&operator=and', }, { index: 5, proxyUrl: - 'search/trade-policy/1?page=5&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=5&count=5&query=&sort=&fuzzy=0&operator=and', }, ], perPage: 5, next: { index: 2, proxyUrl: - 'search/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=0&operator=and', }, previous: { index: 0, @@ -871,7 +871,7 @@ export const productSearchPage1Count5Fetch = { last: { index: 50, proxyUrl: - 'search/trade-policy/1?page=50&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=50&count=5&query=&sort=&fuzzy=0&operator=and', }, }, }, diff --git a/packages/api/mocks/ProductQuery.ts b/packages/api/mocks/ProductQuery.ts index 7bdfa029d8..e4e01377b1 100644 --- a/packages/api/mocks/ProductQuery.ts +++ b/packages/api/mocks/ProductQuery.ts @@ -63,7 +63,7 @@ export const ProductByIdQuery = `query ProductQuery { ` export const productSearchFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A64953394&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A64953394&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { @@ -229,7 +229,7 @@ export const productSearchFetch = { correction: { misspelled: true, }, - fuzzy: 'auto', + fuzzy: '0', operator: 'and', translated: false, pagination: { @@ -237,7 +237,7 @@ export const productSearchFetch = { current: { index: 1, proxyUrl: - 'search/trade-policy/1?page=1&count=1&query=sku:64953394&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=1&count=1&query=sku:64953394&sort=&locale=en-US&hide-unavailable-items=false&operator=and&fuzzy=0', }, before: [], after: [], diff --git a/packages/api/mocks/RedirectQuery.ts b/packages/api/mocks/RedirectQuery.ts index 126e358a32..8f46ce6d1c 100644 --- a/packages/api/mocks/RedirectQuery.ts +++ b/packages/api/mocks/RedirectQuery.ts @@ -6,7 +6,7 @@ export const RedirectQueryTermTech = `query RedirectSearch { ` export const redirectTermTechFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=2&count=1&query=tech&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=2&count=1&query=tech&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/mocks/SearchQuery.ts b/packages/api/mocks/SearchQuery.ts index 443d9ea6e9..d1c7973577 100644 --- a/packages/api/mocks/SearchQuery.ts +++ b/packages/api/mocks/SearchQuery.ts @@ -101,7 +101,7 @@ export const SearchQueryFirst5Products = `query SearchQuery { }` export const productSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { @@ -1348,36 +1348,36 @@ export const productSearchCategory1Fetch = { current: { index: 1, proxyUrl: - 'search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&operator=and&fuzzy=0', }, before: [], after: [ { index: 2, proxyUrl: - 'search/category-1/office/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=2&count=5&query=&sort=&operator=and&fuzzy=0', }, { index: 3, proxyUrl: - 'search/category-1/office/trade-policy/1?page=3&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=3&count=5&query=&sort=&operator=and&fuzzy=0', }, { index: 4, proxyUrl: - 'search/category-1/office/trade-policy/1?page=4&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=4&count=5&query=&sort=&operator=and&fuzzy=0', }, { index: 5, proxyUrl: - 'search/category-1/office/trade-policy/1?page=5&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=5&count=5&query=&sort=&operator=and&fuzzy=0', }, ], perPage: 5, next: { index: 2, proxyUrl: - 'search/category-1/office/trade-policy/1?page=2&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=2&count=5&query=&sort=&operator=and&fuzzy=0', }, previous: { index: 0, @@ -1388,14 +1388,14 @@ export const productSearchCategory1Fetch = { last: { index: 50, proxyUrl: - 'search/category-1/office/trade-policy/1?page=50&count=5&query=&sort=&fuzzy=auto&operator=and', + 'search/category-1/office/trade-policy/1?page=50&count=5&query=&sort=&operator=and&fuzzy=0', }, }, }, } export const attributeSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/facets/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/facets/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/mocks/ValidateCartMutation.ts b/packages/api/mocks/ValidateCartMutation.ts index f46b8b327b..9fbcd0e8da 100644 --- a/packages/api/mocks/ValidateCartMutation.ts +++ b/packages/api/mocks/ValidateCartMutation.ts @@ -347,7 +347,7 @@ export const checkoutOrderFormCustomDataInvalidFetch = { } export const productSearchPage1Count1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A2737806&sort=&fuzzy=auto&locale=en-US&show-invisible-items=true&hideUnavailableItems=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A2737806&sort=&locale=en-US&show-invisible-items=true&hideUnavailableItems=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { @@ -523,7 +523,7 @@ export const productSearchPage1Count1Fetch = { current: { index: 1, proxyUrl: - 'search/trade-policy/1?page=1&count=1&query=sku:2737806&sort=&fuzzy=auto&operator=and', + 'search/trade-policy/1?page=1&count=1&query=sku:2737806&sort=&locale=en-US&hide-unavailable-items=false&operator=and&fuzzy=0', }, before: [], after: [], From 54f842052fcf3035334d545a361b612dd82a5cc4 Mon Sep 17 00:00:00 2001 From: Fanny Chien Date: Mon, 18 Nov 2024 19:14:59 -0300 Subject: [PATCH 5/8] fix: avoids sending duplicate event to IS --- .../templates/SearchPage/SearchWrapper.tsx | 15 ++++++--------- .../src/sdk/product/useProductGalleryQuery.ts | 7 ++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx index 2930b2ddb9..8620a252c4 100644 --- a/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx +++ b/packages/core/src/components/templates/SearchPage/SearchWrapper.tsx @@ -1,12 +1,12 @@ -import { useRouter } from 'next/router' import { useSearch } from '@faststore/sdk' +import { useRouter } from 'next/router' -import type { SearchContentType } from 'src/server/cms' -import type { SearchPageContextType } from 'src/pages/s' -import { useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' -import Section from 'src/components/sections/Section' import EmptyState from 'src/components/sections/EmptyState' import ProductGalleryStyles from 'src/components/sections/ProductGallery/section.module.scss' +import Section from 'src/components/sections/Section' +import type { SearchPageContextType } from 'src/pages/s' +import { useProductGalleryQuery } from 'src/sdk/product/useProductGalleryQuery' +import type { SearchContentType } from 'src/server/cms' import SearchPage from './SearchPage' @@ -28,7 +28,6 @@ export type SearchWrapperProps = { serverData: SearchPageContextType } - export default function SearchWrapper({ itemsPerPage, searchContentType, @@ -39,7 +38,6 @@ export default function SearchWrapper({ state: { term, sort, selectedFacets }, } = useSearch() - const { data: pageProductGalleryData, isValidating } = useProductGalleryQuery( { term, @@ -48,12 +46,11 @@ export default function SearchWrapper({ selectedFacets, } ) - + if (isValidating || !pageProductGalleryData) { return } - // Redirect when there are registered Intelligent Search redirects on VTEX Admin if (pageProductGalleryData?.redirect?.url) { router.replace(pageProductGalleryData?.redirect?.url, null, { diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index f32c01cc3a..148995dbee 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -3,6 +3,7 @@ import { useQuery } from 'src/sdk/graphql/useQuery' import { useSession } from 'src/sdk/session' import { useLocalizedVariables } from './useLocalizedVariables' +import { useSearch } from '@faststore/sdk' import { Facet } from '@faststore/sdk/dist/types' import type { ClientManyProductsQueryQueryVariables, @@ -10,7 +11,6 @@ import type { ClientProductGalleryQueryQueryVariables as Variables, } from '@generated/graphql' import type { IntelligentSearchQueryEvent } from 'src/sdk/analytics/types' -import { useSearch } from '@faststore/sdk' /** * This query is run on the browser and contains @@ -94,7 +94,7 @@ export const useProductGalleryQuery = ({ const queryResult = useQuery(query, localizedVariables, { onSuccess: (data) => { - if (data && term) { + if (data && term && (fuzzyFacetValue || operatorFacetValue)) { import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { sendAnalyticsEvent({ name: 'intelligent_search_query', @@ -115,6 +115,7 @@ export const useProductGalleryQuery = ({ // If there is no fuzzy or operator facet, we need to add them to the selectedFacets and re-fetch the query const shouldRefetchQuery = !queryResult.error && (!fuzzyFacetValue || !operatorFacetValue) + if (shouldRefetchQuery) { if (queryResult.data) { setState({ @@ -130,7 +131,7 @@ export const useProductGalleryQuery = ({ }) } - // The first result is not relevant, so we return data null to avoid rendering the page while the query is being re-fetched + // The first result is not relevant, return null data to avoid rendering the page while the query is being re-fetched return { ...queryResult, isValidating: true, data: null } } From 5594c55ebefa728afd221bf487490ebfb5ad53ea Mon Sep 17 00:00:00 2001 From: Fanny Chien Date: Wed, 20 Nov 2024 15:39:39 -0300 Subject: [PATCH 6/8] fix: Use `search.autocomplete.query` event for suggestions (#2570) ## What's the purpose of this pull request? Follow-up of https://github.com/vtex/faststore/pull/2568 We are currently sending `search.query` events type instead of `search.autocomplete.query` when typing a term in the search input. This may be affecting some metrics. ## How to test it? Run the project locally or test though this [preview link](https://sfj-029e21e--starter.preview.vtex.app/) 1. Type `apple` in the input search field and press enter (you can slowly type it) 2. Open the `Network Tab` and filter for `event` - You might see some events with `type: "search.autocomplete.query"` image - The last event with the full text should have` type: "search.query"` (and it should be the only event with this type). image ### Starters Deploy Preview https://github.com/vtex-sites/starter.store/pull/618 ## References [Slack thread](https://vtex.slack.com/archives/CP6S9HP46/p1720788576536359?thread_ts=1719365308.548959&cid=CP6S9HP46) --- .../src/sdk/analytics/platform/vtex/search.ts | 30 ++++++++++++++++++- packages/core/src/sdk/analytics/types.ts | 14 +++++++++ .../core/src/sdk/search/useSuggestions.ts | 6 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/core/src/sdk/analytics/platform/vtex/search.ts b/packages/core/src/sdk/analytics/platform/vtex/search.ts index ded080d99e..9e3158ad70 100644 --- a/packages/core/src/sdk/analytics/platform/vtex/search.ts +++ b/packages/core/src/sdk/analytics/platform/vtex/search.ts @@ -3,6 +3,7 @@ */ import type { AnalyticsEvent } from '@faststore/sdk' import type { + IntelligentSearchAutocompleteQueryEvent, IntelligentSearchQueryEvent, SearchSelectItemEvent, } from '../../types' @@ -66,6 +67,15 @@ type SearchEvent = url: string type: 'search.query' } + | { + text: string + misspelled: boolean + match: number + operator: string + locale: string + url: string + type: 'search.autocomplete.query' + } const sendEvent = (options: SearchEvent & { url?: string }) => fetch(`https://sp.vtex.com/event-api/v1/${config.api.storeId}/event`, { @@ -86,7 +96,11 @@ const isFullTextSearch = (url: URL) => /^\/s(\/)?$/g.test(url.pathname) const handleEvent = ( - event: AnalyticsEvent | SearchSelectItemEvent | IntelligentSearchQueryEvent + event: + | AnalyticsEvent + | SearchSelectItemEvent + | IntelligentSearchQueryEvent + | IntelligentSearchAutocompleteQueryEvent ) => { switch (event.name) { case 'search_select_item': { @@ -128,6 +142,20 @@ const handleEvent = ( break } + case 'intelligent_search_autocomplete_query': { + sendEvent({ + type: 'search.autocomplete.query', + url: event.params.url, + text: event.params.term, + misspelled: event.params.isTermMisspelled, + match: event.params.totalCount, + operator: event.params.logicalOperator, + locale: event.params.locale, + }) + + break + } + default: } } diff --git a/packages/core/src/sdk/analytics/types.ts b/packages/core/src/sdk/analytics/types.ts index 5cbdb7e698..5735dfeff0 100644 --- a/packages/core/src/sdk/analytics/types.ts +++ b/packages/core/src/sdk/analytics/types.ts @@ -34,3 +34,17 @@ export interface IntelligentSearchQueryEvent { name: 'intelligent_search_query' params: IntelligentSearchQueryParams } + +export interface IntelligentSearchAutocompleteQueryParams { + url: string + locale: string + term: string + logicalOperator: string + isTermMisspelled: boolean + totalCount: number +} + +export interface IntelligentSearchAutocompleteQueryEvent { + name: 'intelligent_search_autocomplete_query' + params: IntelligentSearchAutocompleteQueryParams +} diff --git a/packages/core/src/sdk/search/useSuggestions.ts b/packages/core/src/sdk/search/useSuggestions.ts index 8eab11fe68..d7cc6d5608 100644 --- a/packages/core/src/sdk/search/useSuggestions.ts +++ b/packages/core/src/sdk/search/useSuggestions.ts @@ -7,7 +7,7 @@ import type { ClientSearchSuggestionsQueryQuery as Query, ClientSearchSuggestionsQueryQueryVariables as Variables, } from '@generated/graphql' -import type { IntelligentSearchQueryEvent } from '../analytics/types' +import type { IntelligentSearchAutocompleteQueryEvent } from '../analytics/types' import { useSession } from '../session' @@ -55,8 +55,8 @@ function useSuggestions(term: string) { onSuccess: (callbackData) => { if (callbackData && term) { import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { - sendAnalyticsEvent({ - name: 'intelligent_search_query', + sendAnalyticsEvent({ + name: 'intelligent_search_autocomplete_query', params: { locale, term, From 4de34acedc05ee8c59e6c41c1ef15a139f3de730 Mon Sep 17 00:00:00 2001 From: Fanny Chien Date: Thu, 21 Nov 2024 12:32:16 -0300 Subject: [PATCH 7/8] chore: Ensures that both parameters exists --- packages/core/src/sdk/product/useProductGalleryQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index 148995dbee..df0bdba3b3 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -94,7 +94,7 @@ export const useProductGalleryQuery = ({ const queryResult = useQuery(query, localizedVariables, { onSuccess: (data) => { - if (data && term && (fuzzyFacetValue || operatorFacetValue)) { + if (data && term && fuzzyFacetValue && operatorFacetValue) { import('@faststore/sdk').then(({ sendAnalyticsEvent }) => { sendAnalyticsEvent({ name: 'intelligent_search_query', From d572750ae0f9405b1dc34086ebbe797ad889c9d3 Mon Sep 17 00:00:00 2001 From: Fanny Chien Date: Thu, 21 Nov 2024 12:35:57 -0300 Subject: [PATCH 8/8] chore: Adds default values in case no values are returned avoid loop. --- packages/core/src/sdk/product/useProductGalleryQuery.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/sdk/product/useProductGalleryQuery.ts b/packages/core/src/sdk/product/useProductGalleryQuery.ts index df0bdba3b3..69b3ca1e10 100644 --- a/packages/core/src/sdk/product/useProductGalleryQuery.ts +++ b/packages/core/src/sdk/product/useProductGalleryQuery.ts @@ -122,10 +122,13 @@ export const useProductGalleryQuery = ({ ...state, selectedFacets: [ ...selectedFacets, - { key: 'fuzzy', value: queryResult.data.search.metadata?.fuzzy }, + { + key: 'fuzzy', + value: queryResult.data.search.metadata?.fuzzy ?? 'auto', + }, { key: 'operator', - value: queryResult.data.search.metadata?.logicalOperator, + value: queryResult.data.search.metadata?.logicalOperator ?? 'and', }, ], })