Skip to content

Commit

Permalink
feat: rating resolvers (#2648)
Browse files Browse the repository at this point in the history
## What's the purpose of this pull request?

To add product rating resolvers on graphQL

## How it works?

It defines a new type called: `StoreProductRating` and increments the
query resolver for `product` and for `searchResult`

It was necessary to adapt the `EnhancedSku` type and also to adds a
rating callback to the searchResult's promise resolution

## How to test it?

run the api graphql server locally with the following command:
```bash
yarn dev:server
```
and make a query call

## References

[JIRA Task: SFS-2093](https://vtex-dev.atlassian.net/browse/SFS-2093)

## Checklist

<em>You may erase this after checking them all 😉</em>

**PR Description**

- [ ] Adds graphQL Rating type
- [ ] Increments `product` resolver
- [ ] Increments `searchResult` resolver
  • Loading branch information
Guilera authored Feb 14, 2025
1 parent bde45a0 commit d57303d
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 23 deletions.
10 changes: 10 additions & 0 deletions packages/api/mocks/ProductQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,13 @@ export const productSearchFetch = {
},
},
}

export const productRatingFetch = (productId: string) => ({
info: `https://storeframework.vtexcommercestable.com.br/api/io/reviews-and-ratings/api/rating/${productId}`,
init: undefined,
options: { storeCookies: expect.any(Function) },
result: {
average: 4.5,
totalCount: 20,
},
})
10 changes: 10 additions & 0 deletions packages/api/src/__generated__/schema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/api/src/platforms/vtex/resolvers/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,5 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
},
releaseDate: ({ isVariantOf: { releaseDate } }) => releaseDate ?? '',
advertisement: ({ isVariantOf: { advertisement } }) => advertisement,
rating: (item) => item.rating,
}
12 changes: 11 additions & 1 deletion packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export const Query = {
)
}

const rating = await commerce.rating(sku.itemId)

sku.rating = rating

return sku
} catch (err) {
if (slug == null) {
Expand All @@ -103,9 +107,15 @@ export const Query = {
throw new NotFoundError(`No product found for id ${route.id}`)
}

const rating = await commerce.rating(product.productId)

const sku = pickBestSku(product.items)

return enhanceSku(sku, product)
const enhancedSku = enhanceSku(sku, product)

enhancedSku.rating = rating

return enhancedSku
}
},
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
Expand Down
47 changes: 30 additions & 17 deletions packages/api/src/platforms/vtex/resolvers/searchResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const isRootFacet = (facet: Facet, isDepartment: boolean, isBrand: boolean) =>
export const StoreSearchResult: Record<string, Resolver<Root>> = {
suggestions: async (root, _, ctx) => {
const {
clients: { search },
clients: { search, commerce },
} = ctx

const { searchArgs } = root
const { searchArgs, productSearchPromise } = root

// If there's no search query, suggest the most popular searches.
if (!searchArgs.query) {
Expand All @@ -38,21 +38,26 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
}
}

const { productSearchPromise } = root
const [terms, productSearchResult] = await Promise.all([
search.suggestedTerms(searchArgs),
productSearchPromise,
])

const skus = productSearchResult.products
.map((product) => {
// What determines the presentation of the SKU is the price order
// https://help.vtex.com/pt/tutorial/ordenando-imagens-na-vitrine-e-na-pagina-de-produto--tutorials_278
const maybeSku = pickBestSku(product.items)

return maybeSku && enhanceSku(maybeSku, product)
})
.filter((sku) => !!sku)
const skus = await Promise.all(
productSearchResult.products
.map((product) => {
// What determines the presentation of the SKU is the price order
// https://help.vtex.com/pt/tutorial/ordenando-imagens-na-vitrine-e-na-pagina-de-produto--tutorials_278
const maybeSku = pickBestSku(product.items)

return maybeSku && enhanceSku(maybeSku, product)
})
.filter((sku) => !!sku)
.map(async (sku) => ({
...sku,
rating: await commerce.rating(sku.itemId),
}))
)

const { searches } = terms

Expand All @@ -61,7 +66,11 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
products: skus,
}
},
products: async ({ productSearchPromise }) => {
products: async ({ productSearchPromise }, _, ctx) => {
const {
clients: { commerce },
} = ctx

const productSearchResult = await productSearchPromise

const skus = productSearchResult.products
Expand All @@ -74,6 +83,13 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
})
.filter((sku) => !!sku)

const edges = await Promise.all(
skus.map(async (sku, index) => ({
node: { ...sku, rating: await commerce.rating(sku.itemId) },
cursor: index.toString(),
}))
)

return {
pageInfo: {
hasNextPage: productSearchResult.pagination.after.length > 0,
Expand All @@ -82,10 +98,7 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
endCursor: productSearchResult.recordsFiltered.toString(),
totalCount: productSearchResult.recordsFiltered,
},
edges: skus.map((sku, index) => ({
node: sku,
cursor: index.toString(),
})),
edges,
}
},
facets: async ({ searchArgs }, _, ctx) => {
Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/platforms/vtex/utils/enhanceSku.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Product, Item } from '../clients/search/types/ProductSearchResult'
import { sanitizeHtml } from './sanitizeHtml'

export type EnhancedSku = Item & { isVariantOf: Product }
export type EnhancedSku = Item & { isVariantOf: Product } & {
rating: { average: number; totalCount: number }
}

function sanitizeProduct(product: Product): Product {
return {
Expand All @@ -14,5 +16,9 @@ function sanitizeProduct(product: Product): Product {

export const enhanceSku = (item: Item, product: Product): EnhancedSku => ({
...item,
rating: {
average: 0,
totalCount: 0,
},
isVariantOf: sanitizeProduct(product),
})
4 changes: 4 additions & 0 deletions packages/api/src/typeDefs/product.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ type StoreProduct {
Advertisement information about the product.
"""
advertisement: Advertisement
"""
Product rating.
"""
rating: StoreProductRating!
}

"""
Expand Down
10 changes: 10 additions & 0 deletions packages/api/src/typeDefs/productRating.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type StoreProductRating {
"""
Product average rating.
"""
average: Float!
"""
Product amount of ratings received.
"""
totalCount: Int!
}
21 changes: 17 additions & 4 deletions packages/api/test/queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
pageTypeOfficeDesksFetch,
pageTypeOfficeFetch,
} from '../mocks/CollectionQuery'
import { ProductByIdQuery, productSearchFetch } from '../mocks/ProductQuery'
import {
ProductByIdQuery,
productRatingFetch,
productSearchFetch,
} from '../mocks/ProductQuery'
import {
RedirectQueryTermTech,
redirectTermTechFetch,
Expand Down Expand Up @@ -138,15 +142,19 @@ test('`collection` query', async () => {
})

test('`product` query', async () => {
const fetchAPICalls = [productSearchFetch, salesChannelStaleFetch]
const fetchAPICalls = [
productSearchFetch,
productRatingFetch('64953394'),
salesChannelStaleFetch,
]

mockedFetch.mockImplementation((info, init) =>
pickFetchAPICallResult(info, init, fetchAPICalls)
)

const response = await run(ProductByIdQuery)

expect(mockedFetch).toHaveBeenCalledTimes(2)
expect(mockedFetch).toHaveBeenCalledTimes(3)

fetchAPICalls.forEach((fetchAPICall) => {
expect(mockedFetch).toHaveBeenCalledWith(
Expand Down Expand Up @@ -215,6 +223,11 @@ test('`search` query', async () => {
const fetchAPICalls = [
productSearchCategory1Fetch,
attributeSearchCategory1Fetch,
productRatingFetch('2791588'),
productRatingFetch('44903104'),
productRatingFetch('96175310'),
productRatingFetch('12405783'),
productRatingFetch('24041857'),
salesChannelStaleFetch,
]

Expand All @@ -224,7 +237,7 @@ test('`search` query', async () => {

const response = await run(SearchQueryFirst5Products)

expect(mockedFetch).toHaveBeenCalledTimes(3)
expect(mockedFetch).toHaveBeenCalledTimes(8)

fetchAPICalls.forEach((fetchAPICall) => {
expect(mockedFetch).toHaveBeenCalledWith(
Expand Down

0 comments on commit d57303d

Please sign in to comment.