Skip to content

Commit

Permalink
feat(api): Handle channel as facet for search and product queries (#1197
Browse files Browse the repository at this point in the history
)

* Handle channel as string for search queries

* Apply request changes to fix channel

* Update tests snapshots

* Remove commented code

* Apply suggestion

* Refactor channel

* Fix tests
  • Loading branch information
igorbrasileiro authored Mar 31, 2022
1 parent fe1ec1a commit 00556b1
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 475 deletions.
2 changes: 1 addition & 1 deletion packages/api/mocks/ProductQuery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const ProductByIdQuery = `query ProductQuery {
product(locator: [{key: "id", value: "64953394"}, {key: "channel", value: "1"}]) {
product(locator: [{key: "id", value: "64953394"}]) {
slug
name
productID
Expand Down
12 changes: 6 additions & 6 deletions packages/api/src/platforms/vtex/clients/commerce/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
SimulationOptions,
} from './types/Simulation'
import type { Session } from './types/Session'
import type { Channel } from '../../utils/channel'

const BASE_INIT = {
method: 'POST',
Expand Down Expand Up @@ -43,9 +44,7 @@ export const VtexCommerce = (
checkout: {
simulation: (
args: SimulationArgs,
{ salesChannel }: SimulationOptions = {
salesChannel: ctx.storage.channel,
}
{ salesChannel }: SimulationOptions = ctx.storage.channel
): Promise<Simulation> => {
const params = new URLSearchParams({
sc: salesChannel,
Expand All @@ -62,12 +61,13 @@ export const VtexCommerce = (
orderForm: ({
id,
refreshOutdatedData = true,
salesChannel = ctx.storage.channel,
channel = ctx.storage.channel,
}: {
id: string
refreshOutdatedData?: boolean
salesChannel?: string
channel?: Required<Channel>
}): Promise<OrderForm> => {
const { salesChannel } = channel
const params = new URLSearchParams({
refreshOutdatedData: refreshOutdatedData.toString(),
sc: salesChannel,
Expand All @@ -82,7 +82,7 @@ export const VtexCommerce = (
id,
orderItems,
allowOutdatedData = 'paymentData',
salesChannel = ctx.storage.channel,
salesChannel = ctx.storage.channel.salesChannel,
}: {
id: string
orderItems: OrderFormInputItem[]
Expand Down
6 changes: 5 additions & 1 deletion packages/api/src/platforms/vtex/clients/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetchAPI } from '../fetch'
import type { SelectedFacet } from '../../utils/facets'
import type { ProductSearchResult } from './types/ProductSearchResult'
import type { AttributeSearchResult } from './types/AttributeSearchResult'
import type { IStoreSelectedFacet } from '../../../../__generated__/schema'

export type Sort =
| 'price:desc'
Expand Down Expand Up @@ -35,7 +36,10 @@ export const IntelligentSearch = (
ctx: Context
) => {
const base = `http://portal.${environment}.com.br/search-api/v1/${account}`
const policyFacet = { key: 'trade-policy', value: ctx.storage.channel }
const policyFacet: IStoreSelectedFacet = {
key: 'trade-policy',
value: ctx.storage.channel.salesChannel,
}

const addDefaultFacets = (facets: SelectedFacet[]) => {
const facet = facets.find(({ key }) => key === policyFacet.key)
Expand Down
8 changes: 5 additions & 3 deletions packages/api/src/platforms/vtex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { StoreSearchResult } from './resolvers/searchResult'
import { StoreSeo } from './resolvers/seo'
import type { Loaders } from './loaders'
import type { Clients } from './clients'
import type { Channel } from './utils/channel'
import ChannelMarshal from './utils/channel'

export interface Options {
platform: 'vtex'
Expand All @@ -35,7 +37,7 @@ export interface Context {
* Use it with caution since dependecy injection leads to a more complex code
* */
storage: {
channel: string
channel: Required<Channel>
}
headers: Record<string, string>
}
Expand Down Expand Up @@ -63,9 +65,9 @@ const Resolvers = {
Mutation,
}

export const getContextFactory = (options: Options) => (ctx: any) => {
export const getContextFactory = (options: Options) => (ctx: any): Context => {
ctx.storage = {
channel: options.channel,
channel: ChannelMarshal.parse(options.channel),
}
ctx.clients = getClients(options, ctx)
ctx.loaders = getLoaders(options, ctx)
Expand Down
9 changes: 6 additions & 3 deletions packages/api/src/platforms/vtex/resolvers/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,16 @@ export const StoreProduct: Record<string, Resolver<Root>> = {
} = ctx

const { id, policies } = product
const sellers = policies.find((policy) => policy.id === channel)?.sellers

if (sellers == null) {
const sellers = policies.find(
(policy) => policy.id === channel.salesChannel
)?.sellers

if (sellers === null || sellers === undefined) {
// This error will likely happen when you forget to forward the channel somewhere in your code.
// Make sure all queries that lead to a product are forwarding the channel in context corectly
throw new Error(
`Product with id ${id} has no sellers for channel ${channel}.`
`Product with id ${id} has no sellers for sales channel ${channel.salesChannel}.`
)
}

Expand Down
26 changes: 14 additions & 12 deletions packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ import type {
} from '../../../__generated__/schema'
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
import type { Context } from '../index'
import { mutateChannelContext } from '../utils/channel'

export const Query = {
product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => {
// Insert channel in context for later usage
ctx.storage = {
...ctx.storage,
channel:
locator.find((facet) => facet.key === 'channel')?.value ??
ctx.storage.channel,
const channelString = locator.find((facet) => facet.key === 'channel')
?.value

if (channelString) {
mutateChannelContext(ctx, channelString)
}

const {
loaders: { skuLoader },
} = ctx

return skuLoader.load(locator.map(transformSelectedFacet))
return skuLoader.load(locator.flatMap(transformSelectedFacet))
},
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
const {
Expand All @@ -41,11 +42,12 @@ export const Query = {
ctx: Context
) => {
// Insert channel in context for later usage
ctx.storage = {
...ctx.storage,
channel:
selectedFacets?.find((facet) => facet.key === 'channel')?.value ??
ctx.storage.channel,
const channelString = selectedFacets?.find(
(facet) => facet.key === 'channel'
)?.value

if (channelString) {
mutateChannelContext(ctx, channelString)
}

const after = maybeAfter ? Number(maybeAfter) : 0
Expand All @@ -54,7 +56,7 @@ export const Query = {
count: first,
query: term,
sort: SORT_MAP[sort ?? 'score_desc'],
selectedFacets: selectedFacets?.map(transformSelectedFacet) ?? [],
selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [],
}

return searchArgs
Expand Down
11 changes: 10 additions & 1 deletion packages/api/src/platforms/vtex/utils/channel.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Context } from '..'

export interface Channel {
regionId?: string
salesChannel?: string
}

export default class ChannelMarshal {
public static parse(channelString: string): Channel {
public static parse(channelString: string): Required<Channel> {
try {
const parsedChannel = JSON.parse(channelString) as Channel

Expand All @@ -23,3 +25,10 @@ export default class ChannelMarshal {
return JSON.stringify(channel)
}
}

export const mutateChannelContext = (ctx: Context, channelString: string) => {
ctx.storage = {
...ctx.storage,
channel: ChannelMarshal.parse(channelString),
}
}
12 changes: 9 additions & 3 deletions packages/api/src/platforms/vtex/utils/facets.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import ChannelMarshal from './channel'

export interface SelectedFacet {
key: string
value: string
}

/**
* Transform facets from the store to VTEX platform facets.
* For instance, the channel in Store becomes trade-policy in VTEX's realm
* For instance, the channel in Store becomes trade-policy and regionId in VTEX's realm
* */
export const transformSelectedFacet = ({ key, value }: SelectedFacet) => {
switch (key) {
case 'channel':
return { key: 'trade-policy', value }
case 'channel': {
const channel = ChannelMarshal.parse(value)

// This array should have all values from channel string
return [{ key: 'trade-policy', value: channel.salesChannel }]
}

default:
return { key, value }
Expand Down
Loading

0 comments on commit 00556b1

Please sign in to comment.