Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/skip simulation #27

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
102 changes: 102 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,57 @@ type Query {
hideUnavailableItems: Boolean = false
): ProductSearch @cacheControl(scope: SEGMENT, maxAge: SHORT) @withSegment

"""
Returns products list filtered and ordered.
Is more cacheable than productSearch and is quicker when fetching from catalog.
"""
productSearchNoSimulations(
"""
Terms that is used in search e.g.: eletronics/samsung
"""
query: String = ""
"""
Defines terms types: Brand, Category, Department e.g.: c,b
"""
map: String = ""
"""
Filter by category. {a}/{b} - {a} and {b} are categoryIds
"""
category: String = ""
"""
Array of product specification. specificationFilter_{a}:{b} - {a} is the specificationId, {b} = specification value
"""
specificationFilters: [String]
"""
Filter by price range. e.g.: {a} TO {b} - {a} is the minimum price "from" and {b} is the highest price "to"
"""
priceRange: String = ""
"""
Filter by collection. where collection also know as productClusterId
"""
collection: String = ""
"""
Filter by availability at a specific sales channel. e.g.: salesChannel:4 if want filter by available products for the sales channel 4
"""
salesChannel: String = ""
"""
Order by a criteria. OrderByPriceDESC/OrderByPriceASC, OrderByTopSaleDESC, OrderByReviewRateDESC, OrderByNameASC/OrderByNameDESC, OrderByReleaseDateDESC, OrderByBestDiscountDESC
"""
orderBy: String = "OrderByPriceDESC"
"""
Pagination item start
"""
from: Int = 0
"""
Pagination item end
"""
to: Int = 9
"""
If true, uses isAvailablePerSalesChannel_ parameter on query with segment's sales channel. Will override any given salesChannel arg
"""
hideUnavailableItems: Boolean = false
): ProductSearch @cacheControl(scope: PUBLIC, maxAge: SHORT) @withSegment

searchMetadata(
"""
Terms that is used in search e.g.: eletronics/samsung
Expand Down Expand Up @@ -124,6 +175,57 @@ type Query {
hideUnavailableItems: Boolean = false
): [Product] @cacheControl(scope: SEGMENT, maxAge: SHORT) @withSegment

"""
Returns products list filtered and ordered
Is more cacheable than products and is quicker when fetching from catalog.
"""
productsNoSimulations(
"""
Terms that is used in search e.g.: eletronics/samsung
"""
query: String = ""
"""
Defines terms types: Brand, Category, Department e.g.: c,b
"""
map: String = ""
"""
Filter by category. {a}/{b} - {a} and {b} are categoryIds
"""
category: String = ""
"""
Array of product specification. specificationFilter_{a}:{b} - {a} is the specificationId, {b} = specification value
"""
specificationFilters: [String]
"""
Filter by price range. e.g.: {a} TO {b} - {a} is the minimum price "from" and {b} is the highest price "to"
"""
priceRange: String = ""
"""
Filter by collection. where collection also know as productClusterId
"""
collection: String = ""
"""
Filter by availability at a specific sales channel. e.g.: salesChannel:4 if want filter by available products for the sales channel 4
"""
salesChannel: String = ""
"""
Order by a criteria. OrderByPriceDESC/OrderByPriceASC, OrderByTopSaleDESC, OrderByReviewRateDESC, OrderByNameASC/OrderByNameDESC, OrderByReleaseDateDESC, OrderByBestDiscountDESC
"""
orderBy: String = "OrderByPriceDESC"
"""
Pagination item start
"""
from: Int = 0
"""
Pagination item end
"""
to: Int = 9
"""
If true, uses isAvailablePerSalesChannel_ parameter on query with segment's sales channel. Will override any given salesChannel arg
"""
hideUnavailableItems: Boolean = false
): [Product] @cacheControl(scope: PUBLIC, maxAge: SHORT) @withSegment

productRecommendations(
identifier: ProductUniqueIdentifier
type: CrossSelingInputEnum
Expand Down
1 change: 1 addition & 0 deletions graphql/types/Product.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ type Offer {
List of gifts associated with the product
"""
giftSkuIds: [String]
skippedSimulation: Boolean
}

type Teaser {
Expand Down
35 changes: 24 additions & 11 deletions node/clients/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
} from '@vtex/api'
import { stringify } from 'qs'

import { searchEncodeURI, SearchCrossSellingTypes } from '../resolvers/search/utils'
import {
searchEncodeURI,
SearchCrossSellingTypes,
} from '../resolvers/search/utils'

interface AutocompleteArgs {
maxRows: number | string
Expand Down Expand Up @@ -57,7 +60,9 @@ export class Search extends AppClient {

public product = (slug: string) =>
this.get<SearchProduct[]>(
`/pub/products/search/${this.searchEncodeURI(slug && slug.toLowerCase())}/p`,
`/pub/products/search/${this.searchEncodeURI(
slug && slug.toLowerCase()
)}/p`,
{ metric: 'search-product' }
)

Expand Down Expand Up @@ -115,9 +120,14 @@ export class Search extends AppClient {
{ metric: 'search-productBySku' }
)

public products = (args: SearchArgs, useRaw = false) => {
const method = useRaw ? this.getRaw : this.get
return method<SearchProduct[]>(this.productSearchUrl(args), {
public products = (args: SearchArgs) => {
return this.get<SearchProduct[]>(this.productSearchUrl(args), {
metric: 'search-products',
})
}

public productsRaw = (args: SearchArgs) => {
return this.getRaw<SearchProduct[]>(this.productSearchUrl(args), {
metric: 'search-products',
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe change metric name to be different from products

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its supposed to be the same thing, just had to do this for typings. Its still hitting the same url with the same variables. For me, it should be the same. I will ask for a third opinion.

})
}
Expand All @@ -144,9 +154,9 @@ export class Search extends AppClient {
public facets = (facets: string = '') => {
const [path, options] = decodeURI(facets).split('?')
return this.get<SearchFacets>(
`/pub/facets/search/${this.searchEncodeURI(encodeURI(
`${path.trim()}${options ? '?' + options : ''}`
))}`,
`/pub/facets/search/${this.searchEncodeURI(
encodeURI(`${path.trim()}${options ? '?' + options : ''}`)
)}`,
{ metric: 'search-facets' }
)
}
Expand Down Expand Up @@ -210,11 +220,10 @@ export class Search extends AppClient {
to = 9,
map = '',
hideUnavailableItems = false,
skipSimulation = false,
}: SearchArgs) => {
const sanitizedQuery = this.searchEncodeURI(
encodeURIComponent(
decodeURIComponent(query || '').trim()
)
encodeURIComponent(decodeURIComponent(query || '').trim())
)
if (hideUnavailableItems) {
const segmentData = (this.context as CustomIOContext).segment
Expand Down Expand Up @@ -248,6 +257,10 @@ export class Search extends AppClient {
if (to != null && to > -1) {
url += `&_to=${to}`
}
if (skipSimulation) {
url += '&simulation=false'
}

return url
}
}
104 changes: 92 additions & 12 deletions node/resolvers/search/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { IOContext, NotFoundError, UserInputError } from '@vtex/api'
import { all } from 'bluebird'
import { head, isEmpty, isNil, path, pluck, test, pathOr, zip, tail } from 'ramda'
import {
head,
isEmpty,
isNil,
path,
pluck,
test,
pathOr,
zip,
tail,
} from 'ramda'

import { resolvers as assemblyOptionResolvers } from './assemblyOption'
import { resolvers as autocompleteResolvers } from './autocomplete'
Expand Down Expand Up @@ -115,7 +125,7 @@ const isQueryingMetadata = (info: any) => {
)
}

const filterSpecificationFilters = ({query, map, ...rest}: FacetsArgs) => {
const filterSpecificationFilters = ({ query, map, ...rest }: FacetsArgs) => {
const queryArray = query.split('/')
const mapArray = map.split(',')
const queryAndMap = zip(queryArray, mapArray)
Expand All @@ -124,9 +134,9 @@ const filterSpecificationFilters = ({query, map, ...rest}: FacetsArgs) => {
...tail(queryAndMap).filter(
([_, tupleMap]) => tupleMap === 'c' || tupleMap === 'ft'
),
]
const finalQuery = pluck(0, relevantArgs).join('/')
const finalMap = pluck(1, relevantArgs).join(',')
] as [string, string][]
const finalQuery = pluck<string>(0, relevantArgs).join('/')
const finalMap = pluck<string>(1, relevantArgs).join(',')

return {
...rest,
Expand Down Expand Up @@ -165,12 +175,10 @@ export const queries = {
}
},

facets: async (
_: any,
args: FacetsArgs,
ctx: Context
) => {
const { query, map, hideUnavailableItems } = filterSpecificationFilters(args)
facets: async (_: any, args: FacetsArgs, ctx: Context) => {
const { query, map, hideUnavailableItems } = filterSpecificationFilters(
args
)
const {
clients: { search },
clients,
Expand Down Expand Up @@ -266,6 +274,37 @@ export const queries = {
return search.products(args)
},

productsNoSimulations: async (_: any, args: SearchArgs, ctx: Context) => {
const {
clients: { search },
} = ctx
const queryTerm = args.query
if (queryTerm == null || test(/[?&[\]=]/, queryTerm)) {
throw new UserInputError(
`The query term contains invalid characters. query=${queryTerm}`
)
}

if (args.to && args.to > 2500) {
throw new UserInputError(
`The maximum value allowed for the 'to' argument is 2500`
)
}

const newArgs = { ...args, skipSimulation: true }

const products = await search.products(newArgs)
return products.map(product => {
return {
...product,
items: product.items.map(item => ({
...item,
skippedSimulation: true,
})),
}
})
},

productsByIdentifier: async (
_: any,
args: ProductsByIdentifierArgs,
Expand Down Expand Up @@ -327,10 +366,11 @@ export const queries = {
const translatedArgs = {
...args,
query,
skipSimulation: false,
}

const [productsRaw, searchMetaData] = await all([
search.products(args, true),
search.productsRaw(args),
isQueryingMetadata(info)
? getSearchMetaData(_, translatedArgs, ctx)
: emptyTitleTag,
Expand Down Expand Up @@ -391,4 +431,44 @@ export const queries = {
}
return getSearchMetaData(_, translatedArgs, ctx)
},

productSearchNoSimulations: async (
_: any,
args: SearchArgs,
ctx: Context
) => {
const {
clients,
clients: { search },
vtex,
} = ctx
const queryTerm = args.query
if (queryTerm == null || test(/[?&[\]=]/, queryTerm)) {
throw new UserInputError(
`The query term contains invalid characters. query=${queryTerm}`
)
}

if (args.to && args.to > 2500) {
throw new UserInputError(
`The maximum value allowed for the 'to' argument is 2500`
)
}

const query = await translateToStoreDefaultLanguage(
clients,
vtex,
args.query || ''
)
const translatedArgs = {
...args,
query,
skipSimulation: true,
}
const productsRaw = await search.productsRaw(translatedArgs)
return {
translatedArgs,
productsRaw,
}
},
}
15 changes: 14 additions & 1 deletion node/resolvers/search/productSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,20 @@ export const resolvers = {
const quantity = resources.split('/')[1]
return parseInt(quantity, 10)
},
products: path(['productsRaw', 'data']),
products: ({
translatedArgs: { skipSimulation },
productsRaw: { data },
}: ProductSearchParent) => {
return data.map(product => {
return {
...product,
items: product.items.map(item => ({
...item,
skippedSimulation: !!skipSimulation,
Copy link
Contributor

Choose a reason for hiding this comment

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

Skip simulation is a boolean, !! isn't needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it might be null or undefined, lets say someone pass the wrong value in the resolver above the tree. I think this is safer.

})),
}
})
},
breadcrumb: async (
{ translatedArgs, productsRaw: { data: products } }: ProductSearchParent,
_: any,
Expand Down
Loading