From 923d343bcde8fbebfff9d32ba52ac26cd0504484 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Tue, 5 Oct 2021 09:32:24 -0300 Subject: [PATCH] feat(store-api): Add channel support for products (#968) --- .../store-api/src/__generated__/schema.ts | 24 +++----- .../platforms/vtex/clients/commerce/index.ts | 2 +- .../vtex/clients/{common.ts => fetch.ts} | 0 .../platforms/vtex/clients/search/index.ts | 33 ++++++----- .../src/platforms/vtex/loaders/sku.ts | 19 ++++++- .../src/platforms/vtex/resolvers/query.ts | 56 ++++++------------- .../platforms/vtex/resolvers/validateCart.ts | 2 +- .../src/platforms/vtex/utils/facets.ts | 31 ++++++++++ packages/store-api/src/typeDefs/query.graphql | 16 +----- 9 files changed, 93 insertions(+), 90 deletions(-) rename packages/store-api/src/platforms/vtex/clients/{common.ts => fetch.ts} (100%) create mode 100644 packages/store-api/src/platforms/vtex/utils/facets.ts diff --git a/packages/store-api/src/__generated__/schema.ts b/packages/store-api/src/__generated__/schema.ts index e1cde348dd..04d5455b01 100644 --- a/packages/store-api/src/__generated__/schema.ts +++ b/packages/store-api/src/__generated__/schema.ts @@ -43,6 +43,11 @@ export type IStoreProduct = { sku: Scalars['String']; }; +export type IStoreSelectedFacet = { + key: Scalars['String']; + value: Scalars['String']; +}; + export type Mutation = { __typename?: 'Mutation'; validateCart?: Maybe; @@ -75,14 +80,14 @@ export type QueryAllProductsArgs = { export type QueryProductArgs = { - locator: StoreProductId; + locator: Array; }; export type QuerySearchArgs = { after?: Maybe; first: Scalars['Int']; - selectedFacets?: Maybe>; + selectedFacets?: Maybe>; sort?: Maybe; term?: Maybe; }; @@ -275,16 +280,6 @@ export type StoreProductGroup = { productGroupID: Scalars['String']; }; -export type StoreProductId = { - field: StoreProductIdField; - value: Scalars['ID']; -}; - -export const enum StoreProductIdField { - Id = 'id', - Slug = 'slug' -}; - export type StoreReview = { __typename?: 'StoreReview'; author: StoreAuthor; @@ -303,11 +298,6 @@ export type StoreSearchResult = { products: StoreProductConnection; }; -export type StoreSelectedFacet = { - key: Scalars['String']; - value: Scalars['String']; -}; - export type StoreSeo = { __typename?: 'StoreSeo'; canonical: Scalars['String']; diff --git a/packages/store-api/src/platforms/vtex/clients/commerce/index.ts b/packages/store-api/src/platforms/vtex/clients/commerce/index.ts index c761a60142..2874b7c9c1 100644 --- a/packages/store-api/src/platforms/vtex/clients/commerce/index.ts +++ b/packages/store-api/src/platforms/vtex/clients/commerce/index.ts @@ -1,4 +1,4 @@ -import { fetchAPI } from '../common' +import { fetchAPI } from '../fetch' import type { Simulation, SimulationArgs, diff --git a/packages/store-api/src/platforms/vtex/clients/common.ts b/packages/store-api/src/platforms/vtex/clients/fetch.ts similarity index 100% rename from packages/store-api/src/platforms/vtex/clients/common.ts rename to packages/store-api/src/platforms/vtex/clients/fetch.ts diff --git a/packages/store-api/src/platforms/vtex/clients/search/index.ts b/packages/store-api/src/platforms/vtex/clients/search/index.ts index 0e5eaaea6c..c170e42bf2 100644 --- a/packages/store-api/src/platforms/vtex/clients/search/index.ts +++ b/packages/store-api/src/platforms/vtex/clients/search/index.ts @@ -1,4 +1,5 @@ -import { fetchAPI } from '../common' +import { fetchAPI } from '../fetch' +import type { SelectedFacet } from '../../utils/facets' import type { Options } from '../..' import type { ProductSearchResult } from './types/ProductSearchResult' import type { AttributeSearchResult } from './types/AttributeSearchResult' @@ -13,11 +14,6 @@ export type Sort = | 'discount:desc' | '' -export interface SelectedFacet { - key: string - value: string -} - export interface SearchArgs { query?: string page: number @@ -33,17 +29,20 @@ export interface ProductLocator { value: string } -export const IntelligentSearch = (options: Options) => { - const { channel } = options - const base = `http://search.biggylabs.com.br/search-api/v1/${options.account}` +export const IntelligentSearch = (opts: Options) => { + const { channel } = opts + const base = `http://search.biggylabs.com.br/search-api/v1/${opts.account}` - // TODO: change here once supporting sales channel - const defaultFacets = [ - { - key: 'trade-policy', - value: channel, - }, - ] + const addDefaultFacets = (facets: SelectedFacet[]) => { + const facetsObj = Object.fromEntries( + facets.map(({ key, value }) => [key, value]) + ) + + return Object.entries({ + 'trade-policy': channel, + ...facetsObj, + }).map(([key, value]) => ({ key, value })) + } const search = ({ query = '', @@ -62,7 +61,7 @@ export const IntelligentSearch = (options: Options) => { fuzzy, }) - const pathname = [...defaultFacets, ...selectedFacets] + const pathname = addDefaultFacets(selectedFacets) .map(({ key, value }) => `${key}/${value}`) .join('/') diff --git a/packages/store-api/src/platforms/vtex/loaders/sku.ts b/packages/store-api/src/platforms/vtex/loaders/sku.ts index e5e7ad1118..d6c7cfe68d 100644 --- a/packages/store-api/src/platforms/vtex/loaders/sku.ts +++ b/packages/store-api/src/platforms/vtex/loaders/sku.ts @@ -4,9 +4,22 @@ import { enhanceSku } from '../utils/enhanceSku' import type { EnhancedSku } from '../utils/enhanceSku' import type { Options } from '..' import type { Clients } from '../clients' +import type { SelectedFacet } from '../utils/facets' export const getSkuLoader = (_: Options, clients: Clients) => { - const loader = async (skuIds: readonly string[]) => { + const loader = async (facetsList: readonly SelectedFacet[][]) => { + const skuIds = facetsList.map((facets) => { + const maybeFacet = facets.find(({ key }) => key === 'id') + + if (!maybeFacet) { + throw new Error( + 'Error while loading SKU. Needs to pass an id to selected facets' + ) + } + + return maybeFacet.value + }) + const indexById = skuIds.reduce( (acc, id, index) => ({ ...acc, [id]: index }), {} as Record @@ -42,7 +55,7 @@ export const getSkuLoader = (_: Options, clients: Clients) => { return sorted } - return new DataLoader(loader, { - maxBatchSize: 50, // Warning: Don't change this value, this the max allowed batch size of Search API + return new DataLoader(loader, { + maxBatchSize: 99, // Max allowed batch size of Search API }) } diff --git a/packages/store-api/src/platforms/vtex/resolvers/query.ts b/packages/store-api/src/platforms/vtex/resolvers/query.ts index 8a9fe2a9e6..a033111795 100644 --- a/packages/store-api/src/platforms/vtex/resolvers/query.ts +++ b/packages/store-api/src/platforms/vtex/resolvers/query.ts @@ -1,65 +1,41 @@ import { enhanceSku } from '../utils/enhanceSku' +import { transformSelectedFacet } from '../utils/facets' import { SORT_MAP } from '../utils/sort' -import type { ProductLocator } from '../clients/search' +import type { + QueryProductArgs, + QueryAllCollectionsArgs, + QueryAllProductsArgs, + QuerySearchArgs, +} from '../../../__generated__/schema' import type { CategoryTree } from '../clients/commerce/types/CategoryTree' import type { Context } from '../index' -export interface SelectedFacets { - key: string - value: string -} - -export interface SearchArgs { - term?: string - first: number - after?: string - sort: - | 'price_desc' - | 'price_asc' - | 'orders_desc' - | 'name_desc' - | 'name_asc' - | 'release_desc' - | 'discount_desc' - | 'score_desc' - selectedFacets: SelectedFacets[] -} - export const Query = { - product: async ( - _: unknown, - { locator }: { locator: ProductLocator }, - ctx: Context - ) => { + product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => { const { loaders: { skuLoader }, } = ctx - const skuId = - locator.field === 'id' - ? locator.value - : locator.value.split('-').reverse()[0] - - return skuLoader.load(skuId) + return skuLoader.load(locator.map(transformSelectedFacet)) }, search: async ( _: unknown, - { first, after: maybeAfter, sort, term, selectedFacets }: SearchArgs + { first, after: maybeAfter, sort, term, selectedFacets }: QuerySearchArgs ) => { const after = maybeAfter ? Number(maybeAfter) : 0 const searchArgs = { page: Math.ceil(after / first), count: first, query: term, - sort: SORT_MAP[sort], - selectedFacets, + sort: SORT_MAP[sort ?? 'score_desc'], + selectedFacets: selectedFacets?.map(transformSelectedFacet) ?? [], } return searchArgs }, allProducts: async ( _: unknown, - { first, after: maybeAfter }: { first: number; after: string | null }, + { first, after: maybeAfter }: QueryAllProductsArgs, ctx: Context ) => { const { @@ -91,7 +67,11 @@ export const Query = { })), } }, - allCollections: async (_: unknown, __: unknown, ctx: Context) => { + allCollections: async ( + _: unknown, + __: QueryAllCollectionsArgs, + ctx: Context + ) => { const { clients: { commerce }, } = ctx diff --git a/packages/store-api/src/platforms/vtex/resolvers/validateCart.ts b/packages/store-api/src/platforms/vtex/resolvers/validateCart.ts index 5db58583c0..b654e9532a 100644 --- a/packages/store-api/src/platforms/vtex/resolvers/validateCart.ts +++ b/packages/store-api/src/platforms/vtex/resolvers/validateCart.ts @@ -155,7 +155,7 @@ export const validateCart = async ( orderNumber: updatedOrderForm.orderFormId, acceptedOffer: updatedOrderForm.items.map((item) => ({ ...item, - product: skuLoader.load(item.id), + product: skuLoader.load([{ key: 'id', value: item.id }]), // TODO: add channel })), }, messages: updatedOrderForm.messages.map(({ text, status }) => ({ diff --git a/packages/store-api/src/platforms/vtex/utils/facets.ts b/packages/store-api/src/platforms/vtex/utils/facets.ts new file mode 100644 index 0000000000..6b641a3bfc --- /dev/null +++ b/packages/store-api/src/platforms/vtex/utils/facets.ts @@ -0,0 +1,31 @@ +export interface SelectedFacet { + key: string + value: string +} + +const getIdFromSlug = (slug: string) => { + const id = slug.split('-').pop() + + if (id == null) { + throw new Error('Error while extracting sku id from product slug') + } + + return id +} + +/** + * Transform facets from the store to VTEX platform facets. + * For instance, the channel in Store becames trade-policy in VTEX's realm + * */ +export const transformSelectedFacet = ({ key, value }: SelectedFacet) => { + switch (key) { + case 'channel': + return { key: 'trade-policy', value } + + case 'slug': + return { key: 'id', value: getIdFromSlug(key) } + + default: + return { key, value } + } +} diff --git a/packages/store-api/src/typeDefs/query.graphql b/packages/store-api/src/typeDefs/query.graphql index 5c393976eb..b71896d6a7 100644 --- a/packages/store-api/src/typeDefs/query.graphql +++ b/packages/store-api/src/typeDefs/query.graphql @@ -19,16 +19,6 @@ type StoreCollectionConnection { edges: [StoreCollectionEdge!]! } -enum StoreProductIDField { - id - slug -} - -input StoreProductID { - field: StoreProductIDField! - value: ID! -} - enum StoreSort { price_desc price_asc @@ -40,7 +30,7 @@ enum StoreSort { score_desc } -input StoreSelectedFacet { +input IStoreSelectedFacet { key: String! value: String! } @@ -56,7 +46,7 @@ type StoreSearchResult { } type Query { - product(locator: StoreProductID!): StoreProduct! + product(locator: [IStoreSelectedFacet!]!): StoreProduct! search( # Relay style pagination args. To know more: https://relay.dev/graphql/connections.htm @@ -65,7 +55,7 @@ type Query { after: String sort: StoreSort = score_desc term: String = "" - selectedFacets: [StoreSelectedFacet!] + selectedFacets: [IStoreSelectedFacet!] ): StoreSearchResult! allProducts(first: Int!, after: String): StoreProductConnection!