Skip to content

Commit

Permalink
pick best sku
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Jun 13, 2022
1 parent b8cb7d6 commit 03d8bf2
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 32 deletions.
15 changes: 0 additions & 15 deletions packages/api/src/platforms/vtex/clients/commerce/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fetchAPI } from '../fetch'
import type { Product } from '../search/types/ProductSearchResult'
import type { Context, Options } from '../../index'
import type { Brand } from './types/Brand'
import type { CategoryTree } from './types/CategoryTree'
Expand Down Expand Up @@ -153,19 +152,5 @@ export const VtexCommerce = (
body: '{}',
})
},
search: {
slug: (
slug: string,
options?: { simulation: boolean }
): Promise<Product[]> => {
const params = new URLSearchParams({
simulation: `${options?.simulation ?? false}`, // skip simulation for faster queries
})

return fetchAPI(
`${base}/api/catalog_system/pub/products/search/${slug}/p?${params.toString()}`
)
},
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface CollectionPageType {
url: string
title: string
metaTagDescription: string
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory'
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory' | 'Product'
}

export interface FallbackPageType {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/platforms/vtex/resolvers/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
aggregateRating: () => ({}),
offers: (root) =>
root.sellers
.flatMap((seller) =>
.map((seller) =>
enhanceCommercialOffer({
offer: seller.commertialOffer,
seller,
Expand Down
49 changes: 34 additions & 15 deletions packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BadRequestError } from '../../errors'
import { NotFoundError, BadRequestError } from '../../errors'
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
import { enhanceSku } from '../utils/enhanceSku'
import {
Expand All @@ -19,6 +19,7 @@ import type {
} from '../../../__generated__/schema'
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
import type { Context } from '../index'
import { isValidSkuId, pickBestSku } from '../utils/sku'

export const Query = {
product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => {
Expand All @@ -38,28 +39,46 @@ export const Query = {

const {
loaders: { skuLoader },
clients: { commerce },
clients: { commerce, search },
} = ctx

const skuIdFromSlug = async (s: string) => {
// Standard VTEX PDP routes does not contain skuIds.
const [product] = await commerce.search.slug(s).catch(() => [])
try {
const skuId = id ?? slug?.split('-').pop() ?? ''

if (product) {
return product.items[0].itemId
if (!isValidSkuId(skuId)) {
throw new Error('Invalid SkuId')
}

// We are not in a standard VTEX PDP route, this means we are in a /slug-skuId/p route
return s?.split('-').pop() ?? ''
}
const sku = await skuLoader.load(skuId)

return sku
} catch (err) {
if (slug == null) {
throw new BadRequestError(`Missing slug or id`)
}

const skuId = slug ? await skuIdFromSlug(slug) : id
const route = await commerce.catalog.portal.pagetype(`${slug}/p`)

if (skuId !== null) {
return skuLoader.load(skuId)
}
if (route.pageType !== 'Product' || !route.id) {
throw new NotFoundError(`No product found for slug ${slug}`)
}

throw new BadRequestError(`Missing slug or id`)
const {
products: [product],
} = await search.products({
page: 0,
count: 1,
query: `product:${route.id}`,
})

if (!product) {
throw new NotFoundError(`No product found for id ${route.id}`)
}

const sku = pickBestSku(product.items)

return enhanceSku(sku, product)
}
},
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
const {
Expand Down
29 changes: 29 additions & 0 deletions packages/api/src/platforms/vtex/utils/orderStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* More info at: https://en.wikipedia.org/wiki/Order_statistic
*/

// O(n) search to find the max of an array
export const max = <T>(array: T[], cmp: (a: T, b: T) => number) => {
let best = 0

for (let curr = 1; curr < array.length; curr++) {
if (cmp(array[best], array[curr]) < 0) {
best = curr
}
}

return array[best]
}

// O(n) search to find the max of an array
export const min = <T>(array: T[], cmp: (a: T, b: T) => number) => {
let best = 0

for (let curr = 1; curr < array.length; curr++) {
if (cmp(array[best], array[curr]) > 0) {
best = curr
}
}

return array[best]
}
26 changes: 26 additions & 0 deletions packages/api/src/platforms/vtex/utils/sku.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { min } from './orderStatistics'
import { bestOfferFirst } from './productStock'
import type { Item } from '../clients/search/types/ProductSearchResult'

/**
* This function implements Portal heuristics for returning the best sku for a product.
*
* The best sku is the one with the best (cheapest available) offer
* */
export const pickBestSku = (skus: Item[]) => {
const offersBySku = skus.flatMap((sku) =>
sku.sellers.map((seller) => ({
offer: seller.commertialOffer,
sku,
}))
)

const best = min(offersBySku, ({ offer: o1 }, { offer: o2 }) =>
bestOfferFirst(o1, o2)
)

return best.sku
}

export const isValidSkuId = (skuId: string) =>
skuId !== '' && !Number.isNaN(Number(skuId))

0 comments on commit 03d8bf2

Please sign in to comment.