Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 132 additions & 205 deletions packages/api/src/platforms/vtex/clients/search/index.ts

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions packages/api/src/platforms/vtex/loaders/sku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ export const getSkuLoader = ({ flags }: Options, clients: Clients) => {
(key) => key.split('-')[1] === 'invisibleItems'
)

const { products } = await clients.search.products({
query: `sku:${skuIds.join(';')}`,
page: 0,
count: skuIds.length,
const products = await clients.search.productsByIdentifier({
field: 'sku',
values: skuIds,
showInvisibleItems,
...(flags?.enableUnavailableItemsOnCart && {
hideUnavailableItems: false,
Expand Down
109 changes: 70 additions & 39 deletions packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
import type { ProfileAddress } from '../clients/commerce/types/Profile'
import type { SearchArgs } from '../clients/search'
import type { ProductSearchResult } from '../clients/search/types/ProductSearchResult'
import type { GraphqlContext } from '../index'
import { extractRuleForAuthorization } from '../utils/commercialAuth'
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
Expand All @@ -44,6 +45,16 @@ import { SORT_MAP } from '../utils/sort'
import { FACET_CROSS_SELLING_MAP } from './../utils/facets'
import { StoreCollection } from './collection'

const INVALID_SKU_ID_ERROR = 'Invalid SkuId'
const SLUG_MISMATCH_ERROR =
'Slug was set but the fetched sku does not satisfy the slug condition.'

const shouldFallbackToProductRoute = (error: unknown) =>
isNotFoundError(error) ||
(error instanceof Error &&
(error.message === INVALID_SKU_ID_ERROR ||
error.message.startsWith(SLUG_MISMATCH_ERROR)))

export const Query = {
product: async (
_: unknown,
Expand Down Expand Up @@ -73,7 +84,7 @@ export const Query = {
const skuId = id ?? slug?.split('-').pop() ?? ''

if (!isValidSkuId(skuId)) {
throw new Error('Invalid SkuId')
throw new Error(INVALID_SKU_ID_ERROR)
}

const sku = await skuLoader.load(skuId)
Expand All @@ -97,6 +108,10 @@ export const Query = {

return sku
} catch (err) {
if (!shouldFallbackToProductRoute(err)) {
throw err
}

if (slug == null) {
throw new BadRequestError('Missing slug or id')
}
Expand All @@ -107,15 +122,12 @@ export const Query = {
throw new NotFoundError(`No product found for slug ${slug}`)
}

const {
products: [product],
} = await search.products({
page: 0,
count: 1,
query: `product:${route.id}`,
// Manually disabling this flag to prevent regionalization issues
hideUnavailableItems: false,
})
const product = await search
.fetchProduct({
field: 'id',
value: String(route.id),
})
.catch(() => null)

if (!product) {
throw new NotFoundError(`No product found for id ${route.id}`)
Expand Down Expand Up @@ -162,39 +174,60 @@ export const Query = {
mutateLocaleContext(ctx, locale)
}

let query = term
const after = maybeAfter ? Number(maybeAfter) : 0
const searchArgs: Omit<SearchArgs, 'type'> = {
page: Math.ceil(after / first) || 0,
count: first,
query: term ?? undefined,
sort: SORT_MAP[sort ?? 'score_desc'] ?? SORT_MAP.score_desc,
selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [],
sponsoredCount: sponsoredCount ?? undefined,
}

/**
* In case we are using crossSelling, we need to modify the search
* we will be performing on our search engine. The idea in here
* is to use the cross selling API for fetching the productIds our
* search will return for us.
* Doing this two request workflow makes it possible to have cross
* selling with Search features, like pagination, internationalization
* etc
* In case we are using crossSelling, we fetch product IDs from the
* cross selling API and then hydrate them using the PDP endpoint
* via productsByIdentifier.
*/
if (crossSelling) {
const products = await ctx.clients.commerce.catalog.products.crossselling(
{
const crossSellingProducts =
await ctx.clients.commerce.catalog.products.crossselling({
type: FACET_CROSS_SELLING_MAP[crossSelling.key],
productId: crossSelling.value,
}
)
})

query = `product:${products
const productIds = crossSellingProducts
.map((x) => x.productId)
.slice(0, first)
.join(';')}`
}

const after = maybeAfter ? Number(maybeAfter) : 0
const searchArgs: Omit<SearchArgs, 'type'> = {
page: Math.ceil(after / first) || 0,
count: first,
query: query ?? undefined,
sort: SORT_MAP[sort ?? 'score_desc'],
selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [],
sponsoredCount: sponsoredCount ?? undefined,
const productSearchPromise: Promise<ProductSearchResult> =
ctx.clients.search
.productsByIdentifier({ field: 'id', values: productIds })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are hiting the productsByIdentifier endpoint directly without any dataloader controlling how many requests it should batch as it is done in the getSKULoader. Shouldn't it have some logic to control max request fired simultaneously?

.then((products) => ({
products,
recordsFiltered: products.length,
pagination: {
count: products.length,
current: { index: 0, proxyURL: '' },
before: [],
after: [],
perPage: first,
next: { index: 0, proxyURL: '' },
previous: { index: 0, proxyURL: '' },
first: { index: 0, proxyURL: '' },
last: { index: 0, proxyURL: '' },
},
sampling: false,
options: { sorts: [], counts: [] },
translated: false,
locale: '',
query: '',
operator: 'and',
fuzzy: '0',
searchId: '',
}))

return { searchArgs, productSearchPromise }
}

const productSearchPromise = ctx.clients.search.products(searchArgs)
Expand Down Expand Up @@ -250,14 +283,12 @@ export const Query = {
return []
}

const query = `id:${productIds.join(';')}`
const products = await search.products({
page: 0,
count: productIds.length,
query,
const products = await search.productsByIdentifier({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before. Should we hit this directly without a dataloader controlling the batch size and caching repeated keys requests?

field: 'id',
values: productIds,
})

return products.products
return products
.flatMap((product) =>
product.items.map((sku) => enhanceSku(sku, product))
)
Expand Down
Loading
Loading